SwiftUIでQRコード作成&読み取りを行う
概要
タイトルの通りですが、SwiftUIでQRコードの生成と読み取りを試して見たいと思います。
iOS 13.0以降から👇の便利なメソッドを使えるので、こちらを使って生成していきたいと思います。
このメソッドは、QRコードを画像として生成します。 QRコードは、ISO/IEC 18004:2006規格で定義されている高密度マトリックスバーコードフォーマットです。
QRコード・ジェネレーター・フィルターは、以下のプロパティを使用します
・message: NSData として QR コードとしてエンコードされるデータを表す文字列
・correctionLevel: NSStringとしてエラー訂正形式を表す1文字の文字列。 Lは7パーセント補正、Mは15パーセント補正、Qは25パーセント補正、Hは30パーセント補正。
動作環境
- MBA M3 24GB Sonoma 14.6.1
- Xcode: Version 16.1 (16B40)
シンプルな実装
まずは与えられた文字列をQRコードとして表示するだけのシンプルなViewを作成していきたいと思います。
QrCodeView.swift
を作成し、以下実装を追加します。
import CoreImage.CIFilterBuiltins // ①
import SwiftUI
struct QrCodeView: View {
var data: String
var body: some View {
Image(uiImage: qrImage)
.interpolation(.none) // ②
.resizable() // ③
.scaledToFit()
.accessibilityLabel(Text("QRCode"))
}
private var qrImage: UIImage { // ④
let qrCodeGenerator = CIFilter.qrCodeGenerator()
qrCodeGenerator.message = Data(data.utf8)
qrCodeGenerator.correctionLevel = "H"
if let outputimage = qrCodeGenerator.outputImage {
if let cgImage = CIContext().createCGImage(
outputimage, from: outputimage.extent) {
return UIImage(cgImage: cgImage)
}
}
return UIImage()
}
}
#Preview {
QrCodeView(data: "abc")
.frame(width: 150, height: 150)
}
① :CoreImage.CIFilterBuiltins
のimport
② : 画像拡大縮小時の補間をなくす
※ 補間は、画像を拡大または縮小するときに、ピクセル間を埋めたりスムーズに表示するためのアルゴリズム。QRコードでは読み取りエラーを起こす可能性がある。
③ : アスペクト比を保ちながらリサイズ可能にする
④ : CIFilter.qrCodeGenerator
を使用して出力画像を UIImage
に変換している
👇Preview表示の様子
JSON文字列をQRコード化する
次にJSON文字列を受け取ってQRコードを生成させてみたいと思います。新規にJsonQrCodeView.swift
を作成します。
まずはJSON化するオブジェクトを定義します。仮に Book
というオブジェクトを定義します。
struct Book: Codable {
let id: Int
let title: String
let author: String
let publicationAt: Date
}
この Book
を受け取ってJSON文字列化してQRコード生成します。
struct JsonQrCodeView: View {
var book: Book
var body: some View {
if let json = jsonStr {
QrCodeView(data: json)
.frame(width: 150, height: 150)
} else {
Text("Qr Code Generate Error")
}
}
private var jsonStr: String? {
do {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes]
let jsonData = try encoder.encode(book)
return String(data: jsonData, encoding: .utf8)
} catch {
print(error.localizedDescription)
}
return nil
}
}
#Preview {
let book = Book(
id: 1,
title: "こころ",
author: "夏目漱石",
publicationAt: Date()
)
JsonQrCodeView(book: book)
}
👇Preview表示の様子
こちらをiOSのカメラでフォーカスするとJSON文字列が表示されるかと思います。
QRコード読み取り
次にQRコードの読み取りを実装していきたいと思います。
実装の手段としてはざっと調べて以下の3つの方法があるみたいです。
- サードパーティのライブラリを使う
- 有名どころだと CodeScanner がありそうです
-
AVFoundation
を使用して実装する -
VisionKit
のDataScannerViewController
を使用して実装する- iOS 16以降のみ使用可能
今回はVisionKit
のDataScannerViewController
を試してみたいと思います。
準備
カメラを使うので Info.plist
に Privacy - Camera Usage Description
の設定を追加します。
Value
は何の為に使うか理由を書いとけばOKです。
※ Xcode13以降の場合はデフォルトで Info.plist
は作られないので、「TARGETS」>「Info」タブ >「Custom macOS Application Target Properties」から追加して下さい。
またカメラを使う際に実機でないと使えない為、実機で動作させる為の設定も必要になります。
👇参考URL
実装
新規 JsonQrCodeReaderView.swift
を以下内容で作成します。
import SwiftUI
import VisionKit // ①
struct QRCodeScanner: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> DataScannerViewController {
// ②
let dataScannerViewController = DataScannerViewController(
recognizedDataTypes: [.barcode(symbologies: [.qr])],
isHighlightingEnabled: true
)
try? dataScannerViewController.startScanning() // ③
return dataScannerViewController
}
func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) {
}
}
struct JsonQrCodeReaderView: View {
var body: some View {
QRCodeScanner()
}
}
#Preview {
JsonQrCodeReaderView()
}
① : VisionKit
をimportしています
② : DataScannerViewController
を作成しています
-
recognizedDataTypes
recognizedDataTypes: Set<DataScannerViewController.RecognizedDataType>
-
text(languages:textContentType:)
又はbarcode(symbologies:)
を指定します - 今回は ORコードなので
[.barcode(symbologies: [.qr])]
を指定しています
- isHighlightingEnabled
- ORコードを認識したらハイライトされるように
true
に設定しています
- ORコードを認識したらハイライトされるように
③ : カメラ起動しScanを開始するようにしています
👇実機で動作させている様子
読み取った内容を表示
最後に、読み取ったQRコードの内容を画面上に表示させてみたいと思います。
👇 完成形の実装になります。
struct QRCodeScanner: UIViewControllerRepresentable {
// ①
var dataScannerViewController = DataScannerViewController(
recognizedDataTypes: [.barcode(symbologies: [.qr])],
isHighlightingEnabled: true
)
func makeUIViewController(context: Context) -> DataScannerViewController {
dataScannerViewController.delegate = context.coordinator
try? dataScannerViewController.startScanning()
return dataScannerViewController
}
func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) {
}
// ②
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, DataScannerViewControllerDelegate {
private let parent: QRCodeScanner
private var textView: UIView?
init(_ parent: QRCodeScanner) {
self.parent = parent
}
func dataScanner(_ dataScanner: DataScannerViewController,
didAdd addedItems: [RecognizedItem],
allItems: [RecognizedItem]
) {
guard case .barcode(let barcode) = addedItems.first else {
return
}
// ③
if let text = barcode.payloadStringValue {
let frame = CGRect(
x: barcode.bounds.topLeft.x,
y: barcode.bounds.topLeft.y,
width: abs(barcode.bounds.topRight.x - barcode.bounds.topLeft.x) + 15,
height: abs(barcode.bounds.topLeft.y - barcode.bounds.bottomLeft.y) + 15
)
let textView = UITextView(frame: frame)
textView.font = UIFont.systemFont(ofSize: 10)
textView.text = text
parent.dataScannerViewController.overlayContainerView.addSubview(textView)
self.textView = textView
}
}
// ④
func dataScanner(_ dataScanner: DataScannerViewController,
didRemove removedItems: [RecognizedItem],
allItems: [RecognizedItem]
) {
self.textView?.removeFromSuperview()
}
}
}
① : Delegateから使えるように DataScannerViewController
を外出ししています
② : DataScannerViewControllerDelegate
でイベントのやりとりができる様に makeCoordinator
で Coordinator
クラスを返しています
③ : QRコードが認識されたら呼ばれます。barcode: RecognizedItem.Barcode
の bounds
に認識した領域がセットされているので、その領域に読み取った文字列を UITextView
として作成し、dataScannerViewControllerの overlayContainerView
にViewを追加しています
④ : QRコードが認識が停止されたら呼ばれます。今回は1つだけしか認識させない前提なので、③で作成した UITextView
を削除してます
👇実機で動作させている様子
参考URL
Discussion