Memo

メモ > 技術 > IDE: Xcode > SwiftUI+QRコード

SwiftUI+QRコード
※今ならVisionフレームワークを使う方がいいのかもしれない プレビューでのリアルタイムな検出ができるかは要検証 少なくとも 「QRコードが見つかった → 画像データとして一時保存 → その画像内から改めてQRコードを探す」 のような手順を踏めば対応できないことはなさそう ※1次元バーコード読み取りの精度に難がある場合、以下ライブラリで精度向上が期待できるかもしれない(未検証) [iOS] ZXingObjCを使ってQRコードを読み取る | DevelopersIO https://dev.classmethod.jp/articles/ios-zxing-322-qrcode-decode/ ※以下は参考までにメモしておく 中日新聞:自動車工場のガロア体 QRコードはどう動くか https://static.chunichi.co.jp/chunichi/pages/feature/QR/galois_field_in_auto_factory.html ■QRコード作成 未検証 【SwiftUI】SwiftUIでQRコードを表示する - It’s now or never https://inon29.hateblo.jp/entry/2020/04/25/105335 SwiftUIでQRコードを表示してみる - Qiita https://qiita.com/From_F/items/6c97205fc20cd10a0ddf ■QRコード読み取り SwiftUIでQRコードを読み取る。 - Qiita https://qiita.com/ikaasamay/items/58d1a401e98673a96fd2 Camera preview and a QR-code Scanner in SwiftUI | by Konstantin | Dev Genius https://blog.devgenius.io/camera-preview-and-a-qr-code-scanner-in-swiftui-48b111155c66 【Swift】QRコードを読み取って文字列を取得する - Qiita https://qiita.com/_asa08_/items/8562fe79ec6528a61b06 QRコード(二次元バーコード)作成【無料】 https://www.cman.jp/QRcode/ 【SwiftUI】QRコードを読み込みに使える便利なフレームワーク(MercariQRScanner/CodeScanner) #iOS - Qiita https://qiita.com/tigercat1124/items/6a4c861c58783c273706 「App」で以下の設定で新規作成する Product Name: qrcodereader Interface: SwiftUI Language: Swift Info.plist にKey「Privacy - Camera Usage Description」を追加し、Valueに「QRコードを読み取ります。」と記載しておく ファイルの内容を直接確認すると、dictタグ内に以下が追加されている Info.plist
<key>NSCameraUsageDescription</key> <string>QRコードを読み取ります。</string>
ScannerViewModel.swift
import Foundation class ScannerViewModel: ObservableObject { // QRコードを読み取る間隔 let scanInterval: Double = 1.0 @Published var lastQrCode: String = "" @Published var isShowing: Bool = false // QRコード読み取り時に実行される処理 func onFoundQrCode(_ code: String) { self.lastQrCode = code isShowing = false } }
QrCodeCameraDelegate.swift
import AVFoundation class QrCodeCameraDelegate: NSObject, AVCaptureMetadataOutputObjectsDelegate { var scanInterval: Double = 1.0 var lastTime = Date(timeIntervalSince1970: 0) var onResult: (String) -> Void = { _ in } var mockData: String? 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 } foundBarcode(stringValue) } } @objc func onSimulateScanning(){ foundBarcode(mockData ?? "Simulated QR-code result.") } func foundBarcode(_ stringValue: String) { let now = Date() if now.timeIntervalSince(lastTime) >= scanInterval { lastTime = now self.onResult(stringValue) } } }
CameraPreview.swift
import UIKit import AVFoundation class CameraPreview: UIView { private var label: UILabel? var previewLayer: AVCaptureVideoPreviewLayer? var session = AVCaptureSession() weak var delegate: QrCodeCameraDelegate? init(session: AVCaptureSession) { super.init(frame: .zero) self.session = session } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc func onClick(){ delegate?.onSimulateScanning() } override func layoutSubviews() { super.layoutSubviews() previewLayer?.frame = self.bounds } }
QrCodeScannerView.swift
import SwiftUI import AVFoundation struct QrCodeScannerView: UIViewRepresentable { var supportedBarcodeTypes: [AVMetadataObject.ObjectType] = [.qr] typealias UIViewType = CameraPreview private let session = AVCaptureSession() private let delegate = QrCodeCameraDelegate() private let metadataOutput = AVCaptureMetadataOutput() func interval(delay: Double) -> QrCodeScannerView { delegate.scanInterval = delay return self } func found(result: @escaping (String) -> Void) -> QrCodeScannerView { delegate.onResult = result return self } func setupCamera(_ uiView: CameraPreview) { if let backCamera = AVCaptureDevice.default(for: AVMediaType.video) { if let input = try? AVCaptureDeviceInput(device: backCamera) { session.sessionPreset = .photo if session.canAddInput(input) { session.addInput(input) } if session.canAddOutput(metadataOutput) { session.addOutput(metadataOutput) metadataOutput.metadataObjectTypes = supportedBarcodeTypes metadataOutput.setMetadataObjectsDelegate(delegate, queue: DispatchQueue.main) } let previewLayer = AVCaptureVideoPreviewLayer(session: session) uiView.backgroundColor = UIColor.gray previewLayer.videoGravity = .resizeAspectFill uiView.layer.addSublayer(previewLayer) uiView.previewLayer = previewLayer session.startRunning() } } } func makeUIView(context: UIViewRepresentableContext<QrCodeScannerView>) -> QrCodeScannerView.UIViewType { let cameraView = CameraPreview(session: session) checkCameraAuthorizationStatus(cameraView) return cameraView } static func dismantleUIView(_ uiView: CameraPreview, coordinator: ()) { uiView.session.stopRunning() } private func checkCameraAuthorizationStatus(_ uiView: CameraPreview) { let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) if cameraAuthorizationStatus == .authorized { setupCamera(uiView) } else { AVCaptureDevice.requestAccess(for: .video) { granted in DispatchQueue.main.sync { if granted { self.setupCamera(uiView) } } } } } func updateUIView(_ uiView: CameraPreview, context: UIViewRepresentableContext<QrCodeScannerView>) { uiView.setContentHuggingPriority(.defaultHigh, for: .vertical) uiView.setContentHuggingPriority(.defaultLow, for: .horizontal) } }
ContentView.swift
import SwiftUI struct ContentView: View { @ObservedObject var viewModel = ScannerViewModel() var body: some View { VStack { Text("QRコードリーダー") // カメラ起動ボタン Button(action: { viewModel.isShowing = true }) { Text("カメラ起動") } .fullScreenCover(isPresented: $viewModel.isShowing) { QrCodeReaderView(viewModel: viewModel) } .padding() // 読み取ったQRコード if (viewModel.lastQrCode != "") { Text("QRコード = [ " + viewModel.lastQrCode + " ]") } } } } #Preview { ContentView() }
QrCodeReaderView.swift
import SwiftUI struct QrCodeReaderView: View { @ObservedObject var viewModel: ScannerViewModel var body: some View { Text("QRコード読み取り") ZStack { // QRコード読み取り QrCodeScannerView() .interval(delay: self.viewModel.scanInterval) .found(result: self.viewModel.onFoundQrCode) // 閉じるボタン VStack { Spacer() Button("閉じる") { self.viewModel.isShowing = false } .padding() } } } }

Advertisement