[iOS] QR Code Scanner 만들기 - AvFoundation 이용

2020. 10. 16. 13:44iOS

반응형

최근데 카메라를 이용하는 프로젝트가 있었는데 여기서 활용했던 QR Code 스캔 기능에 대해 포스팅을 해보려고 합니다.

 

기본적으로 만들기 위해서 iOS Architecture에서 Media 영역에 속하는 AvFoundation을 이용하였습니다.

간단하게 AvFoundation을 이용하는 경우를 공식문서에서 찾아보면, iPhone의 카메라, 오디오 등을 이용하는 경우에 사용하면 된다고 명시되어 있습니다. 즉, 여러분들은 카메라를 이용해 사진, 동영상 촬영 + 오디오 녹음이 필요할 때 가장 먼저 AvFoundation을 생각하시면 될 것 같습니다.

 

그럼 AvFoundation을 이용해서 QR Code Scanner을 만들기 전에 사전 준비 작업 먼저 해주겠습니다.

위의 그림과 같이 QR Code가 인식되면 QR Code 겉 표면에 프레임이 생기게 만들어 줄 것입니다.

다음과 같이 UIView을 이용해서 색상을 지정하고 자유롭게 조정해 줄 것입니다.

// UIViewController 최상단에 선언해주세요.
// QR Code가 인식되었을 때, QR Code 바탕에 띄우는 화면이다.
var qrCodeView: UIView = {
    var tempFrameView = UIView()
    tempFrameView.layer.borderColor = UIColor.green.cgColor
    tempFrameView.layer.borderWidth = 2
    return tampFrameView
}

private func initQRCodeFrameView() {
    view.addSubview(qrCodeFrameView)
    view.bringSubviewToFront(qrCodeFrameView)
}

 

그리고 다음으로 AvFoundation의 카메라 기능을 사용하기 위해서 꼭 선언해야하는 변수가 있습니다.

바로 AVCaptureSession 인스턴스 입니다.

 

이 레퍼런스 타입의 역할을 공식문서에서 찾아보면,

캡쳐 작업을 관리하고 입력 장치(카메라)의 흐름을 관리하고 출력(사진을 찍는 행위)을 관리한다고 되어있습니다.

 

어쨋던 뭔가 사진을 찍거나 동영상을 찍거나 할 때, 카메라로부터 어떤 것들을 가져오고 이를 활용해서 뭔가를 보여주는 것 같죠⁉️

그렇기 때문에, 꼭 이 레퍼런스 타입의 인스턴스를 생성하고 입력(Input), 출력(Ouput)에 대한 설정을 해주어야 합니다.

 

지금부터 3가지 작업을 해 줄 것입니다.

 

1️⃣어떤 장치의 입력(Input), 출력(Output)을 쓸 것인지

 

2️⃣입력(Input) 장치로 어떤 장치를 쓸 것인지

 

3️⃣출력(Output) 데이터로 어떤 데이터를 쓸 것인지

 

차례대로 해볼게요. 어떤 장치의 입력, 출력을 쓸 것인지 입니다.

어떤 말인지 지금은 이해가 안되도 코드를 보면 직관적이기 때문에 바로 알 수 있을거에요.

// AVCaptureSession은 계속 사용할 것이기 때문에 지금 선언해주세요.
var captureSession = AVCaptureSession()
var cameraDevice: AVCaptureDevice?

// 카메라 장치 설정 - 뒷면으로 설정
private func initCameraDevice() {
    guard let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
        print("Failed to get the camera device")
        return
    }
        
    cameraDevice = captureDevice
}

보시면 iPhone 뒷면의 카메라를 활용하고 카메라 모드를 뭐 .builtInWideAngleCamera로 활용한다는 것 같죠⁉️

또한 .video인 이유는 QR 코드를 인식하기 전에는 동영상처럼 계속 화면이 보이죠⁉️

요러한 이유로 코드는 다음과 같습니다.

 

.bulitInwideAngleCamera의 모드들이 궁금하신 분들은 여기로 들어가보세요.

.video에 대한 것이 궁금하신 분들은 여기로 들어가보세요.

 

이제 입력 장치에 대한 선언을 해줄게요.

위에서 선언했던 cameraDevice 변수를 활용하여야 합니다.

// 카메라 Input 설정
private func initCameraInputData() {
    if let cameraDevice = self.cameraDevice {
        do {
            let input = try AVCaptureDeviceInput(device: cameraDevice)
            if captureSession.canAddInput(input) { captureSession.addInput(input) }
        } catch {
            print(error.localizedDescription)
            return
        }
    }
}

 

차례로 출력 데이터에 대한 선언을 해줄게요.

저희는 QR Code 데이터를 사용할 것이기 때문에, 이에 대한 선언이라고 이해하시면 될 것 같습니다.

// 당연히 지금까지는 Delegate에 대한 프로토콜을 채택하지 않았기 때문에, 빨간 줄이 뜰 것입니다.
private func initCameraOutputData() {
    let captureMetadataOutput = AVCaptureMetadataOutput()
    if captureSession.canAddOutput(captureMetadataOutput) { captureSession.addOutput(captureMetadataOutput) }
        
    // Output 데이터가 들어왔을 때, 처리할 Delegate 설정
    // Camera로 들어오는 데이터 타입이 QR코드 임을 명시
    captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
    captureMetadataOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]
}

만약 카메라 촬영을 활용하고 싶으면 AVCaptureMetadataOutput이 아니라 AVCapturePhotoOutput을 생성해서 만들어주면 됩니다.

 

이제 장치, 입력, 출력 설정에 대한 모든 것은 완료되었습니다.

 

여기까지만 했다면 아직 화면에 카메라 화면이 출력이 되지 않을 거에요.

이제 카메라 화면을 현재 View에 표시해주는 작업이 필요합니다.

 

이를 위해서 꼭 활용해야하는 레퍼런스 타입이 있습니다.

바로 AVCaptureVideoPreviewLayer 입니다.

 

공식 문서에도 명시가 되어 있습니다.

AVCaptureVideoPreviewLayer는 CALayer의 서브클래스로 입력 장치로부터 들어오는 비디오 화면을 표시하기 위해서 사용해야한다.

 

여기도 바로 코드로 따라해보게습니다.

var videoPreviewLayer: AVCaptureVideoPreviewLayer?

private func displayPreview() {
    videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
    videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
    DispatchQueue.main.async {
        self.videoPreviewLayer?.frame = self.view.layer.bounds
        self.view.layer.addSublayer(self.videoPreviewLayer!)
    }
        
    // startRunning을 실행시켜야 화면이 보이게 됩니다.
    DispatchQueue.global(qos: .userInitiated).async {
        self.captureSession.startRunning()
    }
}

AVCaptureVideoPreviewLayer을 만들어주고 content모드를 지정해주고 현재 view의 Layer에 추가해주었습니다.

그리고 최종적으로 catpureSession.startRunning을 실행해주어야 화면이 표시된다는 점 중요합니다.

 

이제 작업이 준비되었으니, viewDidLoad에서 전부 불러주도록 할게요.

override func viewDidLoad() {
    initCameraDevice()
    initCameraInputData()
    initCameraOutputData()
    displayPreview()
    initQRCodeFrameView()
}

여기까지 성공적으로 따라하셨으면, 이제 화면에 카메라가 표시되고 실행이 되셨을겁니다.

하지만 결정적으로 아직 QR 코드 인식에 대한 처리는 하지 않았죠⁉️

 

처음에 Delegate을 선언했던거 기억하시나요?

 

이제 여기에 맞는 프로토콜을 채택하고 구현부를 구현해주도록 할게요.

extension ViewController: AVCaptureMetadataOutputObjectsDelegate {
    // MetaData가 들어올 때마다 실행되는 메소드
    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        if metadataObjects.count == 0 {
            self.qrCodeFrameView.transform = CGAffineTransform(scaleX: 0, y: 0)
            return
        }
    
        // MetaData을 사람이 읽을 수 있는 Data로 캐스팅
        guard let metaDataObj = metadataObjects[0] as? AVMetadataMachineReadableCodeObject else {
            print("Fail to cast MetaData as AVMetadataMachineReadableCodeObject")
            return
        }
    
        // QR 데이터인 경우
        if metaDataObj.type == .qr {
            // MetaData을 캡쳐한 직접적인 화면을 가져온다.
    	    let qrCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metaDataObj)
            // 가져온 QR Code 화면을 캡쳐한 위치를 넣어준다.
            self.qrCodeFrameView.frame = qrCodeObject!.bounds
            // 작은 곳에서 커지게 애니메이션
            UIView.animate(withDuration: 0.5) {
                self.qrCodeFrameView.transform = CGAffineTransform(scaleX: 1, y: 1)
            }
      
            // 여기서 직접적으로 가져온 QR Code 데이터를 해독한다.
            guard let qrCodeStringData = metaDataObj.stringValue else { return }
            
            print(qeCodeStringData)
        }
    }
}

처음으로 QR 코드 데이터가 들어오지 않은 경우에는 QR 코드를 표시하는 뷰를 안보이게 했고, 인식이 되면 QR 코드를 인식한 크기만큼 사이즈를 늘려주었습니다.

 

그리고 QR 코드를 읽어온 데이터를 print()을 통해 콘솔창에 표시하였습니다.

여기서의 구현부는 각자 앱에 맞게 자유롭게 구현하시면 될 것 같습니다.

 

 

이상으로 오늘은 AVFoundation을 이용한 QR Code Scanner에 대한 포스팅을 해보았습니다 ☺️

잘못된 점이 있으면 말해주세요~~~


참고 레퍼런스

 

 

 

반응형