メモ > 技術 > 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()
}
}
}
}