目次
概要
最近はQRコードを読み取る場面が増えている。
そのため、今後何かサービスを開始する際もQRコードリーダーがあった方がいいだろう。人が求めるものは何かの読み取りや画像認識が多い印象もあるし。
そのため、最低限のQRコードの読み取り処理を実装する。
ソースコード
TestApp.swift
import SwiftUI
@main
struct TestApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
QrScanView()
}
}
}
QrScanView.swift
import SwiftUI
struct QrScanView: View {
@ObservedObject private(set) var viewModel = QrScanViewModel()
var body: some View {
ZStack {
QrCapturePreview(qrScanViewModel: viewModel)
VStack {
Spacer()
}
DialogView()
}
}
private func DialogView() -> some View {
return ZStack {
Text("")
.alert(isPresented: $viewModel.isShownDialog) {
Alert(title: Text("読み取ったQRコード"),
message: Text(viewModel.qrString),
dismissButton: .default(Text("閉じる")) {}
)
}
}
}
}
struct QrCapturePreview: UIViewRepresentable {
private var qrScanViewModel: QrScanViewModel
init(qrScanViewModel: QrScanViewModel) {
self.qrScanViewModel = qrScanViewModel
}
public func makeUIView(context: Context) -> some UIView {
let cameraView = qrScanViewModel.previewView
cameraView.frame = UIScreen.main.bounds
self.qrScanViewModel.launchQrcodeReader()
return cameraView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
QrScanViewModel
import AVFoundation
import Foundation
import UIKit
class QrScanViewModel: NSObject, ObservableObject {
@Published var isShownDialog = false
@Published private(set) var qrString = ""
var previewView: UIView = UIView()
private let metadataOutput = AVCaptureMetadataOutput()
private var session: AVCaptureSession?
func launchQrcodeReader() {
if let session = self.session {
DispatchQueue.global(qos: .background).async {
session.startRunning()
}
return
}
let session = AVCaptureSession()
guard let device = AVCaptureDevice.default(.builtInDualWideCamera, for: .video, position: .back),
let deviceInput = try? AVCaptureDeviceInput(device: device) else {
return
}
switch UIDevice.current.userInterfaceIdiom {
case .phone:
session.sessionPreset = .high
default:
session.sessionPreset = .photo
}
if session.canAddInput(deviceInput) {
session.addInput(deviceInput)
}
if session.canAddOutput(metadataOutput) {
session.addOutput(metadataOutput)
}
metadataOutput.metadataObjectTypes = [.qr]
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
DispatchQueue.main.async {
let videoPreviewLayer = AVCaptureVideoPreviewLayer(session: session)
videoPreviewLayer.videoGravity = .resizeAspectFill
videoPreviewLayer.connection?.videoOrientation = .portrait
videoPreviewLayer.frame = self.previewView.bounds
self.previewView.layer.insertSublayer(videoPreviewLayer, at: 0)
}
DispatchQueue.global(qos: .background).async {
session.startRunning()
}
self.session = session
}
}
extension QrScanViewModel: AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
for metadataObject in metadataObjects {
guard let machineReadableCode = metadataObject as? AVMetadataMachineReadableCodeObject,
machineReadableCode.type == .qr,
let stringValue = machineReadableCode.stringValue else {
return
}
self.qrString = stringValue
self.isShownDialog = true
}
}
}
デモ動画
詳細
まずカメラの映像を表示する
基本的にはカメラの映像を取得する点は同じ。(ただし、カメラの撮影ボタンを押下した時の処理は削除)
QRコード読み取り処理
QRコードの読み取り処理は以下の部分。基本的に、読み取ったものがQRコードだった場合にその情報の文字列を取得する。
extension QrScanViewModel: AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
for metadataObject in metadataObjects {
guard let machineReadableCode = metadataObject as? AVMetadataMachineReadableCodeObject,
machineReadableCode.type == .qr,
let stringValue = machineReadableCode.stringValue else {
return
}
self.qrString = stringValue
self.isShownDialog = true
}
}
}
上記の処理が動くようにするために以下の実装が必要。
このsetMetadataObjectsDelegateの実装をすることでQRコードを読み取ったら上記の処理が走るようになる。
一応、メインスレッドで行うようにしているが、今のところ特に理由はない。
if session.canAddOutput(metadataOutput) {
session.addOutput(metadataOutput)
}
metadataOutput.metadataObjectTypes = [.qr]
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)