🍁
Swift: VisionKitでOCRやQRコードのスキャンをサクッと実装
ボタンを押したらスキャン用のシートが開き、テキストを読み取ったりQRコードを読み取ったりできるようにします。
サンプル:テキストフィールドにフォーカスするとスキャン用のボタンが表示される
サンプル:テキストをスキャンして文字列がテキストフィールドに反映される
サンプル:QRコードをスキャンしてURLがテキストフィールドに反映される
まず、カメラを使うので権限に対する説明をCustom iOS Target Propertiesに追加しておく必要があります。
Key | Value |
---|---|
Privacy - Camera Usage Description | For scanning texts or QR codes. |
次に、VisionKitのDataScannerViewControllerを使ってUIViewControllerRepresentable
を実装してSwiftUIで扱えるようにします。
import SwiftUI
import VisionKit
private struct Scanner: UIViewControllerRepresentable {
@Environment(\.dismiss) var dismiss
@Binding var value: String?
var type: ScanningType
func makeUIViewController(context: Context) -> DataScannerViewController {
// ここでスキャナーの仕様をいろいろ設定する
let controller = DataScannerViewController(
recognizedDataTypes: [type.dataType],
qualityLevel: .accurate,
recognizesMultipleItems: false,
isHighFrameRateTrackingEnabled: false,
isHighlightingEnabled: true
)
controller.delegate = context.coordinator
try? controller.startScanning()
return controller
}
func updateUIViewController(
_ controller: DataScannerViewController,
context: Context
) {}
static func dismantleUIViewController(
_ controller: DataScannerViewController,
coordinator: Coordinator
) {
controller.stopScanning()
}
func makeCoordinator() -> Coordinator {
Coordinator { value in
self.value = value
dismiss()
}
}
final class Coordinator: NSObject, DataScannerViewControllerDelegate {
private let onScan: (String?) -> Void
init(onScan: @escaping (String?) -> Void) {
self.onScan = onScan
}
// 認識した対象物をタップした時に呼び出されるdelegate関数
func dataScanner(
_ dataScanner: DataScannerViewController,
didTapOn item: RecognizedItem
) {
switch item {
case let .barcode(value):
onScan(value.payloadStringValue)
case let .text(value):
onScan(value.transcript)
@unknown default:
break
}
}
}
}
// 今回はテキストとQRコードの両方に対応するため、Type Safeを意識してenumにした
enum ScanningType {
case text
case qrCode
var dataType: DataScannerViewController.RecognizedDataType {
switch self {
case .text:
return .text(languages: ["ja"])
case .qrCode:
return .barcode(symbologies: [.qr])
}
}
}
SwiftUIのシートで、かつModifier形式で呼び出せるようにします。
extension View {
func scanner(isPresented: Binding<Bool>, value: Binding<String?>, type: ScanningType) -> some View {
sheet(isPresented: isPresented) {
Scanner(value: value, type: type)
}
}
}
最後に作ったUIViewControllerRepresentable
を使います。キーボードの上にボタンを配置したかったので、ToolbarItemGroup(placement: .keyboard)
を使いました。
import SwiftUI
struct ContentView: View {
@State var text = ""
@State var showingScanner = false
@State var scanningType = ScanningType.text
var body: some View {
VStack {
TextField(text: $text, axis: .vertical) {
EmptyView()
}
.textFieldStyle(.roundedBorder)
.labelsHidden()
.lineLimit(10, reservesSpace: true)
Spacer()
}
.padding()
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button {
scanningType = .text
showingScanner = true
} label: {
Image(systemName: "text.viewfinder")
}
Button {
scanningType = .qrCode
showingScanner = true
} label: {
Image(systemName: "qrcode.viewfinder")
}
}
}
.scanner(
isPresented: $showingScanner,
value: Binding<String?>(get: { nil }, set: { text += $0 ?? "" }),
type: scanningType
)
}
}
VisionKitだとめっちゃ実装簡単ですね。
Discussion