Upgrade to Pro — share decks privately, control downloads, hide ads and more …

楽しく簡単に!QRコードの読み取り機能を実装しよう

 楽しく簡単に!QRコードの読み取り機能を実装しよう

こちらは iOSDC Japan 2024 のトークで使用したスライドです。

https://iosdc.jp/2024/
https://fortee.jp/iosdc-japan-2024/proposal/737bea3f-e4ce-4358-81d2-a96177d56004

penguinsan

August 22, 2024
Tweet

Other Decks in Programming

Transcript

  1. struct AVFoundationQRCodeScanner: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController {

    let viewController = UIViewController() return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } }
  2. struct AVFoundationQRCodeScanner: UIViewControllerRepresentable { private let session = AVCaptureSession() private

    let sessionQueue = DispatchQueue(label: "sessionQueue") func makeUIViewController(context: Context) -> UIViewController { let viewController = UIViewController() return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } }
  3. struct AVFoundationQRCodeScanner: UIViewControllerRepresentable { private let session = AVCaptureSession() private

    let sessionQueue = DispatchQueue(label: "sessionQueue") func makeUIViewController(context: Context) -> UIViewController { let viewController = UIViewController() sessionQueue.async { self.configureSession(metadataObjectTypes: [.qr], delegate: context.coordinator) self.session.startRunning() } return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } } private extension AVFoundationQRCodeScanner { func configureSession( metadataObjectTypes: [AVMetadataObject.ObjectType], delegate: AVCaptureMetadataOutputObjectsDelegate ) { defer { session.commitConfiguration() } session.beginConfiguration() } }
  4. struct AVFoundationQRCodeScanner: UIViewControllerRepresentable { private let session = AVCaptureSession() private

    let sessionQueue = DispatchQueue(label: "sessionQueue") func makeUIViewController(context: Context) -> UIViewController { let viewController = UIViewController() sessionQueue.async { self.configureSession(metadataObjectTypes: [.qr], delegate: context.coordinator) self.session.startRunning() } return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } } private extension AVFoundationQRCodeScanner { func configureSession( metadataObjectTypes: [AVMetadataObject.ObjectType], delegate: AVCaptureMetadataOutputObjectsDelegate ) { defer { session.commitConfiguration() } session.beginConfiguration() let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) guard let videoDevice else { return } do { let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice) if session.canAddInput(videoDeviceInput) { session.addInput(videoDeviceInput) } } catch { return } } }
  5. struct AVFoundationQRCodeScanner: UIViewControllerRepresentable { @Binding var recognizedPayload: String private let

    session = AVCaptureSession() private let sessionQueue = DispatchQueue(label: "sessionQueue") private let metadataOutput = AVCaptureMetadataOutput() private let metadataObjectQueue = DispatchQueue(label: "metadataObjectQueue") init(recognizedPayload: Binding<String>) { self._recognizedPayload = recognizedPayload } func makeUIViewController(context: Context) -> UIViewController { let viewController = UIViewController() sessionQueue.async { self.configureSession(metadataObjectTypes: [.qr], delegate: context.coordinator) self.session.startRunning() } return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } final class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate { private let parent: AVFoundationQRCodeScanner init(parent: AVFoundationQRCodeScanner) { self.parent = parent } func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard let metadataObject = metadataObjects.first, let machineReadableCode = metadataObject as? AVMetadataMachineReadableCodeObject, machineReadableCode.type == .qr, let stringValue = machineReadableCode.stringValue else { parent.recognizedPayload = "" return } parent.recognizedPayload = stringValue } } } private extension AVFoundationQRCodeScanner { func configureSession( metadataObjectTypes: [AVMetadataObject.ObjectType], delegate: AVCaptureMetadataOutputObjectsDelegate ) { defer { session.commitConfiguration() } session.beginConfiguration() let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) guard let videoDevice else { return } do { let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice) if session.canAddInput(videoDeviceInput) { session.addInput(videoDeviceInput) } } catch { return } if session.canAddOutput(metadataOutput) { session.addOutput(metadataOutput) metadataOutput.metadataObjectTypes = metadataObjectTypes metadataOutput.setMetadataObjectsDelegate(delegate, queue: metadataObjectQueue) } } }
  6. struct AVFoundationQRCodeScanner: UIViewControllerRepresentable { @Binding var recognizedPayload: String private let

    session = AVCaptureSession() private let sessionQueue = DispatchQueue(label: "sessionQueue") private let metadataOutput = AVCaptureMetadataOutput() private let metadataObjectQueue = DispatchQueue(label: "metadataObjectQueue") private let previewLayer: AVCaptureVideoPreviewLayer init(recognizedPayload: Binding<String>) { self._recognizedPayload = recognizedPayload self.previewLayer = Self.makePreviewLayer(session: self.session) } func makeUIViewController(context: Context) -> UIViewController { let viewController = UIViewController() viewController.view.layer.masksToBounds = true viewController.view.layer.addSublayer(previewLayer) previewLayer.frame = viewController.view.layer.bounds sessionQueue.async { self.configureSession(metadataObjectTypes: [.qr], delegate: context.coordinator) self.session.startRunning() } return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { previewLayer.frame = uiViewController.view.layer.bounds } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } final class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate { private let parent: AVFoundationQRCodeScanner init(parent: AVFoundationQRCodeScanner) { self.parent = parent } func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard let metadataObject = metadataObjects.first, let machineReadableCode = metadataObject as? AVMetadataMachineReadableCodeObject, machineReadableCode.type == .qr, let stringValue = machineReadableCode.stringValue else { parent.recognizedPayload = "" return } parent.recognizedPayload = stringValue } } } private extension AVFoundationQRCodeScanner { func configureSession( metadataObjectTypes: [AVMetadataObject.ObjectType], delegate: AVCaptureMetadataOutputObjectsDelegate ) { defer { session.commitConfiguration() } session.beginConfiguration() let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) guard let videoDevice else { return } do { let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice) if session.canAddInput(videoDeviceInput) { session.addInput(videoDeviceInput) } } catch { return } if session.canAddOutput(metadataOutput) { session.addOutput(metadataOutput) metadataOutput.metadataObjectTypes = metadataObjectTypes metadataOutput.setMetadataObjectsDelegate(delegate, queue: metadataObjectQueue) } } } private extension AVFoundationQRCodeScanner { static func makePreviewLayer(session: AVCaptureSession) -> AVCaptureVideoPreviewLayer { let layer = AVCaptureVideoPreviewLayer(session: session) layer.videoGravity = .resizeAspectFill layer.connection?.videoOrientation = .portrait return layer } }
  7. struct AVFoundationQRCodeScanner: UIViewControllerRepresentable { @Binding var recognizedPayload: String private let

    session = AVCaptureSession() private let sessionQueue = DispatchQueue(label: "sessionQueue") private let metadataOutput = AVCaptureMetadataOutput() private let metadataObjectQueue = DispatchQueue(label: "metadataObjectQueue") private let previewLayer: AVCaptureVideoPreviewLayer init(recognizedPayload: Binding<String>) { self._recognizedPayload = recognizedPayload self.previewLayer = Self.makePreviewLayer(session: self.session) } func makeUIViewController(context: Context) -> UIViewController { let viewController = UIViewController() viewController.view.layer.masksToBounds = true viewController.view.layer.addSublayer(previewLayer) previewLayer.frame = viewController.view.layer.bounds sessionQueue.async { self.configureSession(metadataObjectTypes: [.qr], delegate: context.coordinator) self.session.startRunning() } return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { previewLayer.frame = uiViewController.view.layer.bounds } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } final class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate { private let parent: AVFoundationQRCodeScanner init(parent: AVFoundationQRCodeScanner) { self.parent = parent } func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard let metadataObject = metadataObjects.first, let machineReadableCode = metadataObject as? AVMetadataMachineReadableCodeObject, machineReadableCode.type == .qr, let stringValue = machineReadableCode.stringValue else { parent.recognizedPayload = "" return } parent.recognizedPayload = stringValue } } } private extension AVFoundationQRCodeScanner { func configureSession( metadataObjectTypes: [AVMetadataObject.ObjectType], delegate: AVCaptureMetadataOutputObjectsDelegate ) { defer { session.commitConfiguration() } session.beginConfiguration() let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) guard let videoDevice else { return } do { let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice) if session.canAddInput(videoDeviceInput) { session.addInput(videoDeviceInput) } } catch { return } if session.canAddOutput(metadataOutput) { session.addOutput(metadataOutput) metadataOutput.metadataObjectTypes = metadataObjectTypes metadataOutput.setMetadataObjectsDelegate(delegate, queue: metadataObjectQueue) } } } private extension AVFoundationQRCodeScanner { static func makePreviewLayer(session: AVCaptureSession) -> AVCaptureVideoPreviewLayer { let layer = AVCaptureVideoPreviewLayer(session: session) layer.videoGravity = .resizeAspectFill layer.connection?.videoOrientation = .portrait return layer } } 😫
  8. struct VisionKitQRCodeScanner: UIViewControllerRepresentable { @Binding var recognizedPayload: String func makeUIViewController(context:

    Context) -> DataScannerViewController { let dataScannerViewController = DataScannerViewController( recognizedDataTypes: [.barcode(symbologies: [.qr])] ) dataScannerViewController.delegate = context.coordinator try? dataScannerViewController.startScanning() return dataScannerViewController } func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } final class Coordinator: NSObject, DataScannerViewControllerDelegate { private let parent: VisionKitQRCodeScanner init(parent: VisionKitQRCodeScanner) { self.parent = parent } func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) { guard case .barcode(let barcode) = addedItems.first else { return } if let payloadStringValue = barcode.payloadStringValue { parent.recognizedPayload = payloadStringValue } } func dataScanner(_ dataScanner: DataScannerViewController, didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) { parent.recognizedPayload = "" } } } ΊͪΌͪ͘Όγϯϓϧʂʂʂ
  9. struct VisionKitQRCodeScanner: UIViewControllerRepresentable { @Binding var recognizedPayload: String func makeUIViewController(context:

    Context) -> DataScannerViewController { let dataScannerViewController = DataScannerViewController( recognizedDataTypes: [.barcode(symbologies: [.qr])] ) dataScannerViewController.delegate = context.coordinator try? dataScannerViewController.startScanning() return dataScannerViewController } func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } final class Coordinator: NSObject, DataScannerViewControllerDelegate { private let parent: VisionKitQRCodeScanner init(parent: VisionKitQRCodeScanner) { self.parent = parent } func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) { guard case .barcode(let barcode) = addedItems.first else { return } if let payloadStringValue = barcode.payloadStringValue { parent.recognizedPayload = payloadStringValue } } func dataScanner(_ dataScanner: DataScannerViewController, didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) { parent.recognizedPayload = "" } } } struct AVFoundationQRCodeScanner: UIViewControllerRepresentable { @Binding var recognizedPayload: String private let session = AVCaptureSession() private let sessionQueue = DispatchQueue(label: "sessionQueue") private let metadataOutput = AVCaptureMetadataOutput() private let metadataObjectQueue = DispatchQueue(label: "metadataObjectQueue") private let previewLayer: AVCaptureVideoPreviewLayer init(recognizedPayload: Binding<String>) { self._recognizedPayload = recognizedPayload self.previewLayer = Self.makePreviewLayer(session: self.session) } func makeUIViewController(context: Context) -> UIViewController { let viewController = UIViewController() viewController.view.layer.masksToBounds = true viewController.view.layer.addSublayer(previewLayer) previewLayer.frame = viewController.view.layer.bounds sessionQueue.async { self.configureSession(metadataObjectTypes: [.qr], delegate: context.coordinator) self.session.startRunning() } return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { previewLayer.frame = uiViewController.view.layer.bounds } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } final class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate { private let parent: AVFoundationQRCodeScanner init(parent: AVFoundationQRCodeScanner) { self.parent = parent } func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard let metadataObject = metadataObjects.first, let machineReadableCode = metadataObject as? AVMetadataMachineReadableCodeObject, machineReadableCode.type == .qr, let stringValue = machineReadableCode.stringValue else { parent.recognizedPayload = "" return } parent.recognizedPayload = stringValue } } } private extension AVFoundationQRCodeScanner { func configureSession( metadataObjectTypes: [AVMetadataObject.ObjectType], delegate: AVCaptureMetadataOutputObjectsDelegate ) { defer { session.commitConfiguration() } session.beginConfiguration() let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) guard let videoDevice else { return } do { let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice) if session.canAddInput(videoDeviceInput) { session.addInput(videoDeviceInput) } } catch { return } if session.canAddOutput(metadataOutput) { session.addOutput(metadataOutput) metadataOutput.metadataObjectTypes = metadataObjectTypes metadataOutput.setMetadataObjectsDelegate(delegate, queue: metadataObjectQueue) } } } private extension AVFoundationQRCodeScanner { static func makePreviewLayer(session: AVCaptureSession) -> AVCaptureVideoPreviewLayer { let layer = AVCaptureVideoPreviewLayer(session: session) layer.videoGravity = .resizeAspectFill layer.connection?.videoOrientation = .portrait return layer } } 7JTJPO,JU "7'PVOEBUJPO
  10. ΧϝϥىಈɾϓϨϏϡʔදࣔɾεΩϟϯ։࢝ struct VisionKitQRCodeScanner: UIViewControllerRepresentable { @Binding var recognizedPayload: String func

    makeUIViewController(context: Context) -> DataScannerViewController { let dataScannerViewController = DataScannerViewController( recognizedDataTypes: [.barcode(symbologies: [.qr])] ) dataScannerViewController.delegate = context.coordinator try? dataScannerViewController.startScanning() return dataScannerViewController } func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } final class Coordinator: NSObject, DataScannerViewControllerDelegate { private let parent: VisionKitQRCodeScanner init(parent: VisionKitQRCodeScanner) { self.parent = parent } func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) { guard case .barcode(let barcode) = addedItems.first else { return } if let payloadStringValue = barcode.payloadStringValue { parent.recognizedPayload = payloadStringValue } } func dataScanner(_ dataScanner: DataScannerViewController, didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) { parent.recognizedPayload = "" } } }
  11. ΧϝϥىಈɾϓϨϏϡʔදࣔɾεΩϟϯ։࢝ func makeUIViewController(context: Context) -> DataScannerViewController { let dataScannerViewController =

    DataScannerViewController( recognizedDataTypes: [.barcode(symbologies: [.qr])] ) dataScannerViewController.delegate = context.coordinator try? dataScannerViewController.startScanning() return dataScannerViewController }
  12. ΧϝϥىಈɾϓϨϏϡʔදࣔɾεΩϟϯ։࢝ func makeUIViewController(context: Context) -> DataScannerViewController { let dataScannerViewController =

    DataScannerViewController( recognizedDataTypes: [.barcode(symbologies: [.qr])] ) dataScannerViewController.delegate = context.coordinator try? dataScannerViewController.startScanning() return dataScannerViewController }
  13. 23ίʔυͷ৘ใΛऔಘ struct VisionKitQRCodeScanner: UIViewControllerRepresentable { @Binding var recognizedPayload: String func

    makeUIViewController(context: Context) -> DataScannerViewController { let dataScannerViewController = DataScannerViewController( recognizedDataTypes: [.barcode(symbologies: [.qr])] ) dataScannerViewController.delegate = context.coordinator try? dataScannerViewController.startScanning() return dataScannerViewController } func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } final class Coordinator: NSObject, DataScannerViewControllerDelegate { private let parent: VisionKitQRCodeScanner init(parent: VisionKitQRCodeScanner) { self.parent = parent } func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) { guard case .barcode(let barcode) = addedItems.first else { return } if let payloadStringValue = barcode.payloadStringValue { parent.recognizedPayload = payloadStringValue } } func dataScanner(_ dataScanner: DataScannerViewController, didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) { parent.recognizedPayload = "" } } }
  14. 23ίʔυͷ৘ใΛऔಘ final class Coordinator: NSObject, DataScannerViewControllerDelegate { ... /// σʔλεΩϟφ͕ΞΠςϜͷೝࣝΛ։࢝͢ΔͱԠ౴͠·͢ɻ

    func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) { guard case .barcode(let barcode) = addedItems.first else { return } if let payloadStringValue = barcode.payloadStringValue { parent.recognizedPayload = payloadStringValue } } /// σʔλεΩϟφ͕ΞΠςϜͷೝࣝΛఀࢭͨ͠ͱ͖ʹԠ౴͠·͢ɻ func dataScanner(_ dataScanner: DataScannerViewController, didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) { parent.recognizedPayload = "" } }
  15. 23ίʔυͷ৘ใΛऔಘ final class Coordinator: NSObject, DataScannerViewControllerDelegate { ... /// σʔλεΩϟφ͕ΞΠςϜͷೝࣝΛ։࢝͢ΔͱԠ౴͠·͢ɻ

    func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) { guard case .barcode(let barcode) = addedItems.first else { return } if let payloadStringValue = barcode.payloadStringValue { parent.recognizedPayload = payloadStringValue } } /// σʔλεΩϟφ͕ΞΠςϜͷೝࣝΛఀࢭͨ͠ͱ͖ʹԠ౴͠·͢ɻ func dataScanner(_ dataScanner: DataScannerViewController, didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) { parent.recognizedPayload = "" } }
  16. 23ίʔυλοϓ࣌ͷΞΫγϣϯΛ௥Ճ final class Coordinator: NSObject, DataScannerViewControllerDelegate { ... /// σʔλεΩϟφ͕ೝࣝͨ͠ΞΠςϜΛਓ͕λοϓ͢ΔͱԠ౴͠·͢ɻ

    func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) { guard case .barcode(let barcode) = item else { return } if let payloadStringValue = barcode.payloadStringValue, let url = URL(string: payloadStringValue) { UIApplication.shared.open(url) } } }
  17. struct VisionKitQRCodeScanner: UIViewControllerRepresentable { @Binding var recognizedPayload: String func makeUIViewController(context:

    Context) -> DataScannerViewController { let dataScannerViewController = DataScannerViewController( recognizedDataTypes: [.barcode(symbologies: [.qr])], isHighlightingEnabled: true ) dataScannerViewController.delegate = context.coordinator try? dataScannerViewController.startScanning() return dataScannerViewController } func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) { } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } final class Coordinator: NSObject, DataScannerViewControllerDelegate { private let parent: VisionKitQRCodeScanner init(parent: VisionKitQRCodeScanner) { self.parent = parent } func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) { guard case .barcode(let barcode) = addedItems.first else { return } if let payloadStringValue = barcode.payloadStringValue { parent.recognizedPayload = payloadStringValue } } func dataScanner(_ dataScanner: DataScannerViewController, didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) { parent.recognizedPayload = "" } func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) { guard case .barcode(let barcode) = item else { return } if let payloadStringValue = barcode.payloadStringValue, let url = URL(string: payloadStringValue) { UIApplication.shared.open(url) } } } }