ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • QR code - Scan!
    오늘의 Swift 상식 2021. 9. 21. 16:18
    728x90
    반응형

     

     

     

     

     

     

    안녕하세요. 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

    댓글

Designed by Tistory.