Share
Embed
楽しく簡単に!QRコードの読み取り機能を実装しよう
こちらは iOSDC Japan 2024 のトークで使用したスライドです。
https://iosdc.jp/2024/
https://fortee.jp/iosdc-japan-2024/proposal/737bea3f-e4ce-4358-81d2-a96177d56004
penguinsan
August 22, 2024
Transcript
48 slides
-
struct AVFoundationQRCodeScanner: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController {
let viewController = UIViewController() return viewController } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } } -
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) { } } -
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() } } -
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 } } } -
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) } } } -
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 } } -
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 } } 😫 -
7JTJPOWT7JTJPO,JU w ίϯϐϡʔλϏδϣϯΞϧΰϦζϜΛ ར༻ͯ͠ɺը૾ॲཧɾಈըॲཧΛߦ͏ w ػցֶशϞσϧΛར༻ w ΧϝϥΛར༻ͨ͠ใͷݕग़ɺ
औಘͨ͠ใΛදࣔ͢Δ6*Λఏڙ͢Δ w ෦ͷը૾ॲཧͰ7JTJPO͕ಈ͍͍ͯΔʁ 7JTJPO 7JTJPO,JU -
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 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 -
ΧϝϥىಈɾϓϨϏϡʔදࣔɾεΩϟϯ։࢝ 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 = "" } } } -
ΧϝϥىಈɾϓϨϏϡʔදࣔɾεΩϟϯ։࢝ func makeUIViewController(context: Context) -> DataScannerViewController { let dataScannerViewController =
DataScannerViewController( recognizedDataTypes: [.barcode(symbologies: [.qr])] ) dataScannerViewController.delegate = context.coordinator try? dataScannerViewController.startScanning() return dataScannerViewController } -
ΧϝϥىಈɾϓϨϏϡʔදࣔɾεΩϟϯ։࢝ func makeUIViewController(context: Context) -> DataScannerViewController { let dataScannerViewController =
DataScannerViewController( recognizedDataTypes: [.barcode(symbologies: [.qr])] ) dataScannerViewController.delegate = context.coordinator try? dataScannerViewController.startScanning() return dataScannerViewController } -
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 = "" } } } -
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 = "" } } -
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 = "" } } -
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) } } } -
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) } } } }