0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DataScannerViewControllerを使ってiOSアプリ内でのQRコード読み取りをシンプルに実装する

Last updated at Posted at 2024-12-09

はじめに

DataScannerViewControllerはiOS 16以降で利用可能なクラスです。
VisionKitフレームワークによって提供されており、iPhoneのカメラを使ったQRコードやテキストの読み取り機能を実装することができます。

iOSアプリでQRコードの読み取り機能を実装する場合、今まではAVFoundationの複数のクラスを利用するやや複雑な実装が必要でしたが、DataScannerViewControllerを使うことで、とてもシンプルな実装でリッチなUIの作成が可能になります。
この記事では、DataScannerViewControllerの使い方とQRコード読み取り機能の実装例について簡単に紹介します。

Vision、VisionKitとDataScannerViewController

Appleが提供するフレームワークにはVisionKit以外にもVisionがありますが、これらは別のフレームワークです。

Visionは、コンピュータビジョンアルゴリズムを利用した画像処理・動画処理を行うためのフレームワークです。
このフレームワークは機械学習モデルを保持しており、画像内の物体検出や軌跡の追跡などの機能を提供しています。

VisionKitは、カメラを利用した情報の検出や取得した情報を表示するUIを提供するフレームワークです。
おそらくVisionKitの内部ではVisionフレームワークを使った画像処理が実行されているので、Visionの画像処理機能にI/Oの機能を追加したフレームワークがVisionKitだと考えても良さそうです。

Visionの画像処理では機械学習モデルを利用するため、画像処理を行うためにはある程度パワーのあるプロセッサが必要になります。そのため、DataScannerViewControllerは2018年以降のApple Neural Engineを搭載した端末でなければ利用できません。
DataScannerViewControllerのクラスプロパティであるisSupportedでDataScannerViewControllerが利用可能な端末かを判定できるので、スキャンを開始する前にこのプロパティを使って判定する必要があることに注意しましょう。

WWDC22のセッションで言及されている箇所

WWDC22での、DataScannerViewControllerが対応しているデバイスについての説明

VisionKitで機械可読コードやテキストをキャプチャする - WWDC22 - ビデオ - Apple Developerより)

DataScannerViewControllerを使ってQRコードを読み取る

ここからはDataScannerViewControllerを使って、SwiftUIベースのアプリにできるだけシンプルなコードでQRコード読み取り機能を実装したいと思います。

※カメラ利用のためのプライバシー権限の設定やisSupportedを使った対応デバイスの確認などについては、説明を省略します。

スキャナを起動して画面に表示する

DataScannerViewControllerはUIViewControllerのサブクラスなので、SwiftUIで表示する場合はUIViewControllerRepresentableを利用します。

makeUIViewController()メソッドでDataScannerViewControllerのインスタンスを作成します。
この中でstartScanning()を呼び出して、QRコードのスキャンを開始します。

import SwiftUI
import VisionKit

struct QRCodeScanner: UIViewControllerRepresentable {

    func makeUIViewController(context: Context) -> DataScannerViewController {
        let dataScannerViewController = DataScannerViewController(
            recognizedDataTypes: [.barcode(symbologies: [.qr])]  // 識別するデータの種類を指定
        )
        try? dataScannerViewController.startScanning()  // スキャンの開始
        return dataScannerViewController
    }
    
    func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) {
    }
}
実行例

スキャナを起動して画面に表示するデモ

QRコード認識時にハイライトを表示する

DataScannerViewControllerのイニシャライザでisHighlightingEnabled引数にtrueを渡すと、QRコードを認識した際にハイライトが表示されるようになります。

    func makeUIViewController(context: Context) -> DataScannerViewController {
        let dataScannerViewController = DataScannerViewController(
            recognizedDataTypes: [.barcode(symbologies: [.qr])],
            isHighlightingEnabled: true  // 認識された項目の周囲にハイライトを表示する
        )
        try? dataScannerViewController.startScanning()
        return dataScannerViewController
    }
実行例

QRコード認識時にハイライトを表示するデモ

スキャナが認識中のQRコードのデータを取得する

DataScannerViewControllerがQRコードを認識した時のイベントは、DataScannerViewControllerDelegateでハンドリングします。
スキャナがアイテムを認識した際はdataScanner(_:didAdd:allItems:)が呼び出され、アイテムの認識が外れた時にはdataScanner(_:didRemove:allItems:)が呼び出されます。

struct QRCodeScanner: UIViewControllerRepresentable {

    @Binding var recognizedPayload: String

    func makeUIViewController(context: Context) -> DataScannerViewController {
        let dataScannerViewController = DataScannerViewController(
            recognizedDataTypes: [.barcode(symbologies: [.qr])],
            isHighlightingEnabled: true
        )
        dataScannerViewController.delegate = context.coordinator  // Delegate を設定
        try? dataScannerViewController.startScanning()
        return dataScannerViewController
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    final class Coordinator: NSObject, DataScannerViewControllerDelegate {

        private let parent: QRCodeScanner

        init(_ qrCodeScanner: QRCodeScanner) {
            self.parent = qrCodeScanner
        }

        // スキャナがアイテムの認識を開始すると呼ばれる
        func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) {
            guard case .barcode(let barcode) = addedItems.first else {
                return
            }

            if let payloadStringValue = barcode.payloadStringValue {
                parent.recognizedPayload = payloadStringValue
            }
        }

        // スキャナがアイテムの認識を停止した時に呼ばれる
        func dataScanner(_ dataScanner: DataScannerViewController, didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) {
            parent.recognizedPayload = ""
        }
    }
}
実行例

スキャナが認識中のQRコードのデータを取得するデモ

QRコードをタップした時に処理を実行する

ユーザーが認識中のQRコードをタップすると、DataScannerViewControllerDelegateのdataScanner(_:didTapOn:)が呼び出されます。
これを利用して、「QRコードをタップしたらブラウザでWebページを開く」などの動作を実装できます。

        // スキャナが認識したアイテムをユーザーがタップすると呼ばれる
        func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) {
            guard case .barcode(let barcode) = item else {
                return
            }

            if let payloadStringValue = barcode.payloadStringValue,
               let url = URL(string: payloadStringValue)
            {
                UIApplication.shared.open(url)
            }
        }
実行例

QRコードをタップした時に処理を実行するデモ


QRコード読み取りの処理を、少ないコードでとてもシンプルに実装できました 🎉
今回作成したQRCodeScannerの最終的な状態は以下のようになります。

import SwiftUI
import VisionKit

struct QRCodeScanner: UIViewControllerRepresentable {

    @Binding var recognizedPayload: String

    func makeUIViewController(context: Context) -> DataScannerViewController {
        let dataScannerViewController = DataScannerViewController(
            recognizedDataTypes: [.barcode(symbologies: [.qr])],
            isHighlightingEnabled: true
        )
        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

        init(_ qrCodeScanner: QRCodeScanner) {
            self.parent = qrCodeScanner
        }

        func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) {
            guard case .barcode(let barcode) = addedItems.first else {
                return
            }

            if let payloadStringValue = barcode.payloadStringValue {
                parent.recognizedPayload = payloadStringValue
            }
        }

        func dataScanner(_ dataScanner: DataScannerViewController, didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) {
            parent.recognizedPayload = ""
        }

        func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) {
            guard case .barcode(let barcode) = item else {
                return
            }

            if let payloadStringValue = barcode.payloadStringValue,
               let url = URL(string: payloadStringValue)
            {
                UIApplication.shared.open(url)
            }
        }
    }
}

参考資料

おまけ

iOSDC Japan 2024で、DataScannerViewControllerについて発表をしました。
YouTubeには発表のアーカイブも残っているので、よければこちらもご覧ください。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?