-
QR code - Scan!오늘의 Swift 상식 2021. 9. 21. 16:18728x90반응형
안녕하세요. iOS 개발자 에이든입니다!👦🏻
지난 시간에 이어서
QR Code Scan!!
우리 식당에서 찍는 그 앱!!
지금 바로 만들어보자구요ㅎㅎ
바로 레쓰고~🎶
QR Scanner 만들기
1. 카메라 권한 주기!
Scanner를 구현하려면 카메라 사용은 필수겠죠? 카메라 사용을 위해서는 사용자에게 권한을 허락받아야 하니깐 Info.plist에서 Privacy - Camera Usage Description을 추가해주고, 권한 요청할 때 띄우고자 하는 메시지를 입력해주세요!
그럼 앱이 실행되어 최초로 카메라 기능을 사용할 때 권한을 요청합니다!
2. Scanner View 구성하기
간단하게 View를 구성해보겠습니다! QRCameraCell(cardNumber: $cardNumber) 부분이 카메라 화면을 보여주고 QR을 Scan 하는 부분이고 그 밑에 Text는 읽어낸 값을 보여줍니다.
import SwiftUI struct ContentView: View { // QR을 Scan했을 때 읽어낸 값 @State private var cardNumber: String = "" var body: some View { VStack { Spacer().frame(height: 70) // QR Scanner QRCameraCell(cardNumber: $cardNumber) Spacer() // Scan 한 값을 보여주는 Text Text(cardNumber) .padding(EdgeInsets(top: 0, leading: 15, bottom: 0, trailing: 15)) .minimumScaleFactor(0.1) .frame(width: UIScreen.main.bounds.width - 48, height: 48, alignment: .leading) .border(Color.gray, width: 1) Spacer().frame(height: 70) } } }
3. Camera를 활용한 Scanner 구현하기
자! 대망의 Scanner를 구현할 차례입니다. 이 부분이 아무래도 가장 길 것 같네요ㅠ 간략히 말씀드리자면 카메라 및 QR Scan을 위한 라이브러리로 AVFoundation을 활용할 꺼구요. 코드가 SwiftUI로 되어있다 보니 UIViewController를 임의로 만들어 연결해 주는 작업을 할 겁니다! 만약 ViewController로 코드를 짜셨다면 코드에 있는 CustomCameraController만 구현하시면 됩니다!
자세한 설명은 코드를 보면서 주석으로 설명드리도록 하겠습니다!- View
import SwiftUI // 카메라, 오디오 등을 제어하기 위해 사용되는 Apple의 라이브러리 import AVFoundation struct QRCameraCell: View { // 카메라 화면이 들어갈 이미지 @State private var inputImage: UIImage? @Binding var cardNumber: String // Notification은 앱 내에서 특정상황에 값을 넘겨주고 싶을 때 해당 키로 등록되어 있는 Observer들에게 Notification을 하고 그 값을 전달할 수도 있습니다. 다만 많이 사용하게 되면 앱 성능을 저하시킬 수 있으니 주의하세요! // 아래 예시는 "Card Number Recognized By QR" 이라는 Notification에 대한 Observer 입니다. let cardNumberFromController = NotificationCenter.default .publisher(for: NSNotification.Name("Card Number Recognized By QR")) var body: some View { VStack { HStack { Spacer().frame(width: 24) // ViewController를 SwiftUI의 View로 변환하여 보여줍니다. CustomCameraRepresentable(image: self.$inputImage, cardNumber: $cardNumber) Spacer().frame(width: 24) } } // Notification을 받으면 그 값을 cardNumber에 할당 .onReceive(cardNumberFromController) { (output) in cardNumber = output.object as? String ?? "" } } }
- UIViewRepresentable (SwiftUI에서 ViewController의 View를 반환하는 역할)
struct CustomCameraRepresentable: UIViewControllerRepresentable { @Binding var image: UIImage? @Binding var cardNumber: String // ViewController를 만들고 delegate도 설정해줍니다. ViewController가 처음 생성될때만 이 Method가 호출되고 그 후에는 아래에 있는 updateUIViewController Method가 호출됩니다. func makeUIViewController(context: Context) -> CustomCameraController { let controller = CustomCameraController() controller.delegate = context.coordinator return controller } // makeUIViewController Method가 ViewController가 최초 생성될 때만 호출되고 그 이후에는 updateUIViewController Method가 호출되는데, 저희는 이후에 update할일이 없으니 비워두겠습니다. func updateUIViewController(_ cameraViewController: CustomCameraController, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(self) } // Navigation, AVCapturePhotoCapture를 사용할 수 있게 delegate를 설정해줍니다. class Coordinator: NSObject, UINavigationControllerDelegate, AVCapturePhotoCaptureDelegate { let parent: CustomCameraRepresentable init(_ parent: CustomCameraRepresentable) { self.parent = parent } func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let imageData = photo.fileDataRepresentation() { parent.image = UIImage(data: imageData) } } } }
- ViewController
class CustomCameraController: UIViewController, AVCaptureMetadataOutputObjectsDelegate { var image: UIImage? // 실시간 캡처 활동을 관리하고, 입력 장치의 데이터 흐름을 조정하여 출력을 캡처하는 Object var captureSession = AVCaptureSession() // Capture Session에 대한 입력 및 하드웨어별 캡처 기능에 대한 제어를 제공 var backCamera: AVCaptureDevice? var frontCamera: AVCaptureDevice? var currentCamera: AVCaptureDevice? // 캡처된 결과물 var photoOutput: AVCapturePhotoOutput? // Capture Session에서 생성된 메타데이터 var metadataOutput: AVCaptureMetadataOutput? // 캡처된 비디오를 표시하는 Core Animation Layer var cameraPreviewLayer: AVCaptureVideoPreviewLayer? var cardNumber: String = "" // delegate var delegate: AVCapturePhotoCaptureDelegate? func didTapRecord() { let settings = AVCapturePhotoSettings() photoOutput?.capturePhoto(with: settings, delegate: delegate!) } override func viewDidLoad() { super.viewDidLoad() setup() } func setup() { setupCaptureSession() setupDevice() setupInputOutput() setupPreviewLayer() startRunningCaptureSession() } func setupCaptureSession() { // 출력의 품질 설정 captureSession.sessionPreset = AVCaptureSession.Preset.photo } // 디바이스의 어떤 카메라를 사용할지 설정. 여기서는 후면 카메라를 사용합니다. func setupDevice() { let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified) for device in deviceDiscoverySession.devices { switch device.position { case AVCaptureDevice.Position.front: self.frontCamera = device case AVCaptureDevice.Position.back: self.backCamera = device default: break } } self.currentCamera = self.backCamera } func setupInputOutput() { do { let captureDeviceInput = try AVCaptureDeviceInput(device: currentCamera!) captureSession.addInput(captureDeviceInput) photoOutput = AVCapturePhotoOutput() // 출력에 대한 설정 photoOutput?.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])], completionHandler: nil) // Capture Session에 Output 추가 captureSession.addOutput(photoOutput!) metadataOutput = AVCaptureMetadataOutput() // output에 메타데이터가 있는지 확인 후 Capture Session에 추가하는 작업입니다. 저희는 단순히 사진을 찍는것이 아닌 QR도 인식해서 값을 받아와야 하기 때문에 메타데이터도 추가해줍니다. if metadataOutput != nil { captureSession.addOutput(metadataOutput!) metadataOutput?.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) // 메타데이터의 타입은 qr이니 qr의 값을 읽어오면 추가하라는 의미입니다. metadataOutput?.metadataObjectTypes = [.qr] } else { return } } catch { print(error) } } // 카메라 화면이 보이는것에 대한 설정 func setupPreviewLayer() { self.cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) self.cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill self.cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait self.cameraPreviewLayer?.frame = CGRect(x: 0, y: 0, width: self.view.frame.width - 48, height: self.view.frame.width - 48) self.view.layer.insertSublayer(cameraPreviewLayer!, at: 0) } // Capture Session을 시작! 이 Method를 호출하면 카메라 화면이 나오며 우리가 만든 기능들을 동작시키게 됩니다. func startRunningCaptureSession() { captureSession.startRunning() } // 출력된 이미지를 받아와서 값을 확인 후 값이 있으면 Notification 해줍니다! func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { if let metadataObject = metadataObjects.first { guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return } guard let stringValue = readableObject.stringValue else { return } if cardNumber != stringValue { cardNumber = stringValue NotificationCenter.default.post(name: NSNotification.Name("Card Number Recognized By QR"), object: stringValue, userInfo: nil) } } } }
이렇게 하면 아래와 같이 Scanner가 완성됩니다!
자 이렇게 QR을 만들고 Scan 하는 방법에 대해 알아보았습니다.
QR은 일상생활에서도 흔하게 사용되는 만큼 한 번쯤 만들어 보시는 걸 추천드릴게요!
그럼 오늘은 여기까지입니다!
혹시라도 부족하거나 잘못된 부분 그리고 질문 있으시면 언제든 댓글 부탁드려요! 감사합니다!👦🏻👋🏻
728x90반응형'오늘의 Swift 상식' 카테고리의 다른 글
ScreenShot(Save, Share) (0) 2021.10.22 QR Code - 만들기편! (0) 2021.09.15 Protocol 2편 (Delegation, Extension, 상속, 합성) (0) 2021.08.25 Protocol 1편 (Protocol 정의 방법) (0) 2021.08.16 Class의 상속 (0) 2021.08.15