🚲

シーンに応じた使いやすいQRコード読み取り機能を実装しよう(原稿) #iosdc #d

2022/09/12に公開

これはiOSDC Japan 2022での下記のセッションの原稿です。

https://fortee.jp/iosdc-japan-2022/proposal/3a466fb6-b445-4e44-8f5f-b57b7e5b44dc

動画
https://youtu.be/-HZsYjDnYMs

サンプルアプリ
https://github.com/jollyjoester/QRReaderSample

↓↓↓以下原稿↓↓↓

シーンに応じた使いやすいQRコード読み取り機能を実装しよう

タイトル

それでは「シーンに応じた使いやすいQRコード読み取り機能を実装しよう」というタイトルで発表します!

自己紹介

自己紹介

どーも!ジョリージョースターです。
iPhoneに魅せられてエンジニアに転向してから早9年
未だに楽しくiOSアプリ開発を追っています。

ほぼ毎月Swift愛好会という、SwiftやiOSをわいわい楽しむ会をやっています。
毎回「カー→ンパ↑ーイ!🍻」から始まるゆるい会ですので一緒に楽しみたい方ぜひご参加ください!

今年もiOSDCで発表できて嬉しいです!
みなさんわいわいしていきましょう!

このセッションについて

このセッションについて

このセッションは古くからiOSにあるQRコード読み取りについて取り扱います。

知ってる人にとっては当たり前じゃん?みたいな内容も多いと思いますが、「そんな古くからある機能でも深ぼってみるとおもしろい!」とか「基本的な機能だけどお客さまにより良い使い勝手が提供できたら嬉しいよね!」みたいなことを伝えられたらいいなと思っています。

なお、ちょっと作りが雑ですが、本セッション用のサンプルアプリも用意しています。
手元で動かしながら聞いていただくとより楽しんでいただけると思いますのでぜひお試しください。

c.f. QRReaderSample

アジェンダ

アジェンダ

アジェンダです。

そもそもQRコードとは?というところからはじめ、QRコード読み取りの基本、よくある機能を見ていって、最後にいろいろなシーンに応じてどういう風に組み合わせていくといいかもねって流れでお話します。

QRコードとは

それではQRコードとは何かを見ていきましょう〜

QRコード

これですね。
QRコードを見たことがないという人はもういないのではないか?というくらい普及していますよね。

c.f.QRコード - Wikipedia

QRコードとは

QRコードとは

QRコードは1994年に当時のデンソーの開発部門、現在のデンソーウェーブが開発した2次元シンボルです。大容量で高速に情報を読み取れる技術として生まれました。QRというのはQuick Responseに由来しているそうです。

バーコードの次の技術ということで、

  • バーコードでは20文字程度だった容量が数千字に
  • 三つの角にある切り出しシンボルのおかげで高速で360°読み取りが可能に
  • そして誤り訂正で多少の汚れや破損があっても読み取り可能

だったりとパワーアップしています

さらにライセンスフリーで標準規格化されたため、世の中に広く広まり、多様な使われ方をしています。

iOSにおけるQRコード

iOS7からAVFoundationでのQRコード読み取りがサポートされました。
これのおかげで独自のQRリーダーアプリがたくさんリリースされました。

@available(iOS 7.0, *)
public static let qr: AVMetadataObject.ObjectType

iOS11からは標準のカメラアプリでもQRを読み取れるようになりました。
これは便利ですよね。とても重宝してます。

標準カメラでのQR読み取り
標準カメラでのQR読み取り

QRコード読み取りの基本

次にQRコード読み取りの基本についておさらいしていきましょう。

QRコード読み取りの基本

みなさんQRコードを読むときは

  1. iPhoneの後ろ側のカメラ、背面カメラをかざして
  2. カメラに映ったものが画面のPreviewに映され
  3. QRコードがあったら検知する

という手順で読み取っていますよね。

iOSにおいてカメラの入出力を扱うのはAVFoundationです。
なのでAVFoundationを丁寧におさらいしていくことがQRコード読み取り機能を実装する際のポイントに繋がります。

QRコード読み取りの基本 ~AVFoundation~

QRコード読み取りの基本 ~AVFoundation~

AVFoundationの基本的な仕組みは公式ドキュメントのこの図を見ると理解しやすいと思います。

まず真ん中のAVCaptureSessionについてです。
AVCaptureSessionというのはデバイスにアクセスして入力デバイスからメディア出力へいい感じにデータの流れを管理するやつです。

そのセッションにどのようなInputがあってどのようにOutputするのかを設定していきます。

Inputではどのデバイスからの入力を取り扱うかを定めます。

AVCaptureDeviceとしてBackカメラ、マイクなどがあることが図示されていますね。
利用したいデバイスをAVCaptureDeviceInputとしてAVCaptureSessionに追加します。

OutputではそれらのInputをどのようなデータで扱うかを定めます。
この例では写真や動画やPreviewで扱う例が掲載されています。

c.f. Setting Up a Capture Session

QRコード読み取りの基本 ~AVFoundation~ QRの場合

今回QRコードを読み取る例では

  1. AVCaptureSessionを作る
  2. AVCaptureDeviceに背面カメラ、back cameraからの動画、videoを指定
  3. そのDeviceをAVCaptureDeviceInputにセットしてSessionに追加
  4. OutPutとして、QRなどを検知するためのAVCatureMetadataObjectを追加
  5. PreviewのためのPreviewLayerも追加

という感じで実装していきます。

なお、Vision frameworkでもQRコードを検知することができます。
流れはほぼ同じでOutputにVideoDataOutputを使うところが異なります。

QRコード読み取りの基本 ~実装~

カメラの権限リクエスト

具体的な実装を見ていきましょう!
先の図にはありませんが、カメラを使うので権限リクエストが必要ですね。

Info.plistのProperty List KeyとしてNSCameraUsageDescriptionを追加します。

<key>NSCameraUsageDescription</key>
<string>QR読み取るためにカメラ使うよ〜</string>

そして必要なカメラからVideoデータを扱う権限をリクエストします。

    override func viewDidLoad() {
        super.viewDidLoad()
        
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .authorized:
            break // 👍
        case .notDetermined:
	    // 権限をリクエスト!
            AVCaptureDevice.requestAccess(for: .video) { granted in
                if !granted {
                    // 😭
                }
            }
        default:
            // The user has previously denied access.
        }

これで準備OK。

AVCaptureSessionの初期化

次にAVCaptureSessionの初期化をしていきます。

セッション初期化の具体的な処理は自作のconfigureSessionというメソッドを作って実装していきましょう。そのconfigureSessionsessionQueue というキューを作成してその中で実行するようにします。

    private let session = AVCaptureSession()
    private let sessionQueue = DispatchQueue(label: "sessionQueue")
   override func viewDidLoad() {
        super.viewDidLoad()
        
                // カメラへの権限リクエスト
	// ...
		
        sessionQueue.async {
            self.configureSession()
        }
    }

キューで実行する理由は2つあって

  • 1つ目は、複数のスレッドからAVCaptureSessionを同時にいじると安全ではないということ
  • 2つ目は、セッションを開始するためのstartRunning()がブロッキングコールなのでMainとは異なるキューで実行する必要がある(ブロッキングコール=UIをブロックしてしまう)

からなので、セッションをいじるさいにはこのキューを使いましょう。

AVCapureSession初期化開始&Device取得

configureSessionの中を見ていきましょう。

    private func configureSession() {
        session.beginConfiguration()
        
        let defaultVideoDevice = AVCaptureDevice.default(.builtInWideAngleCamera,
                                                         for: .video,
                                                         position: .back)
        
        guard let videoDevice = defaultVideoDevice else {
            session.commitConfiguration()
            return
        }
	// 続く

Sessionの初期化処理ではまずbeginCofiguration()を呼びます。

これはもろもろのセッション初期化処理をバッチで変更するためのものです。
この後のセッションに対する設定を加えていきますが、それらの変更はcommitConfiguration()を呼んだときに反映されます。ここではエラーになったときに、即commitConfiguration()を呼んで設定終了していますね。

次にデバイスを指定しましょう。
AVCaptureDeviceのdefaultメソッドで.buildInWideAngleCamera、これは最も汎用的なカメラデバイスを指していて、このカメラの用途に.videoを指定し、ポジションの.backで背面のカメラを指定します。

これでカメラの動画入力を得るためのvideoDeviceを取得しました。

AVCaptureDeviceInput

先程取得したvideoDeviceをAVCaptureDeviceInputにセットし、Sessionに追加します。
後ほどデバイスの設定変更などで利用するのでこのときのvideoDeviceInputは保持しています。

	//続き
        do {
            let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice)
            
            if session.canAddInput(videoDeviceInput) {
                session.addInput(videoDeviceInput)
                self.videoDeviceInput = videoDeviceInput
            }
        } catch {
            session.commitConfiguration()
            return
        }
	//続く

AVCaptureOutput

お次が、AVCaptureOutputです。

カメラから入力したデータからQRというメタデータを検知するために、AVCaptureMetadataOutputを作成します。さらに、検知された順番でメタデータを受け取るためのシリアルキューも作っておきます。

    private let metadataOutput = AVCaptureMetadataOutput()
    private let metadataObjectQueue = DispatchQueue(label: "metadataObjectQueue")

このmetadataObOutputにをセッションに追加します。

さらにmetadataOutputに、メタデータを検知したときのDelegateメソッドを追加するsetMetadataObjectsDelegateselfを指定します。具体的な処理は次に説明します。実行キューに先ほど作ったキューをセットします。

そして、検知対象のmetadataObjectTypes.qrを指定しましょう。
このmetadataObjectTypesは複数指定できるのでバーコード含めた様々なコードを読み取るようにするといったことも可能です。

	//configureSessionの続き
        if session.canAddOutput(metadataOutput) {
            session.addOutput(metadataOutput)
            
            metadataOutput.setMetadataObjectsDelegate(self, queue: metadataObjectQueue)
            metadataOutput.metadataObjectTypes = [.qr]
        } else {
            session.commitConfiguration()
        }
                        
        session.commitConfiguration()
    }

さいごにcommitConfiguration()でセッションの設定完了です!

AVCaptureMetadataOutputObjectsDelegate

メタデータを検知したときに呼ばれるDelegateメソッドでQRコードの内容を読んでいきましょう。
AVCaptureMetadataOutputObjectsDelegatedidOutputで検知したメタデータを処理します。

得られるメタデータが、metadataObjectsと複数になっているのはカメラに写った複数のメタデータを処理できるからです。ここではforですべて処理していますが、読み込みたい対象のQRコードが1つだけというような場合は最初の1つを使うようにしてもいいと思います。

metadataObjectsに含まれるそれぞれのmetadataObjectが、

  • AVMetadataMachineReadableCodeObjectであり、
  • タイプが.qrであり
  • 文字列の値を取り出せたら

成功!無事QRコード読み取り成功となります👏

extension ViewController: 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
            }
            // QRコード読み取り成功🎉
            print("The content of QR code: \(stringValue)")
        }
    }
}

1点、気をつけておきたいのが、このデリゲートメソッドはメタデータを検知している限り、かなり短い間に連続して呼ばれます。なのでひたすら同じQRコードが読み取られ続けます。

デモ(QR検知する度に音を鳴らすやつ)

QRコードを1つ検知すれば良い場合はすぐセッションを止めて実行したい処理に移りましょう。

連続して異なる複数のQRコードを読み取りたい場合は、いったん読み込んだQRコードの中身などを保存しておき、異なるものを検出した場合にだけ、QRコードを検出したとみなすのが良いでしょう。

e.g. 一度読み込んだQRコードをSetに保存しておく例

+    private var scannedQRs = Set<String>()
        for metadataObject in metadataObjects {
            guard let machineReadableCode = metadataObject as? AVMetadataMachineReadableCodeObject,
                  machineReadableCode.type == .qr,
                  let stringValue = machineReadableCode.stringValue
            else {
                return
            }
            
+            if !self.scannedQRs.contains(stringValue) {
+                self.scannedQRs.insert(stringValue)

                    // QRコード読み取り成功🎉
                print("The content of QR code: \(stringValue)")
+            }
        } 

Preview

背面カメラの画像をPreviewとして表示するには、CALayerのサブクラス、AVCaptureVideoPreviewLayerを使います。

AVCaptureVideoPreviewLayerにセッションを食わせてあとはサイズなり向きなりの設定をし、previewと名付けたUIViewにSublayerとして追加すればOKです。

    @IBOutlet private weak var preview: UIView!
    
    private lazy var previewLayer: AVCaptureVideoPreviewLayer = {
        let layer = AVCaptureVideoPreviewLayer(session: self.session)
        layer.frame = preview.bounds
        layer.videoGravity = .resizeAspectFill
        layer.connection?.videoOrientation = .portrait
        return layer
    }()
    override func viewDidLoad() {
        super.viewDidLoad()
	
	// セッション初期化とか
	
        preview.layer.addSublayer(previewLayer)
    }

session.startRunning

長かったですが、最後、startRunning()でセッションを開始すればQRコードを読み取ることができるようになりました!

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
		
        sessionQueue.async {
            self.session.startRunning()
        }
    }

👏👏👏

QRコード読み取りよくある機能

基本的な読み取りができるようになったので、次にQRコード読み取りによくある機能をひととおり見ていきましょう!

Zoom

まずZoomです。QRコードを読み取る距離がちょっと離れている場合はZoomしてみましょう。
カメラ操作なのでAVCaptureDevicevideoZoomFactorの設定を変更していきます。

    private func setZoomFactor(_ zoomFactor: CGFloat) {
        guard let videoDeviceInput = self.videoDeviceInput else { return }
        do {
            try videoDeviceInput.device.lockForConfiguration()
            videoDeviceInput.device.videoZoomFactor = zoomFactor
            videoDeviceInput.device.unlockForConfiguration()
        } catch {
            print("Could not lock for configuration: \(error)")
        }
    }

なお、このようなデバイスのプロパティを変更するときは排他アクセスをするために、lockForConfiguration()でロックをかける必要があることは覚えておきましょう。

使用例として、1.5倍にしたいときはvideoZoomFactorに1.5を設定するというような使い方をします。
スライダーやピンチなどの操作に応じてZoomFactorを変更しても良いですね。

        setZoomFactor(CGFloat(1.5))

余談ですが、iOS15からAVCapureDeviceに追加されたminimumFocusDistanceというプロパティを用いてZoomを自動調整するというデモがWWDC21のセッションで行われていました。興味がある方はぜひご覧ください。

c.f. What’s new in camera capture

Torch

次はトーチです。トーチは背面カメラのわきにあるライトのことです。
QRコードを読み取るのに明るさが足りない場合はトーチをつけてみましょう。

トーチもAVCaptureDeviceから操作できます。
Zoomとほぼ同様のやり方でdeivceをロックし、torchModeを変更しましょう。

    private func switchTorch(_ mode: AVCaptureDevice.TorchMode) {
        guard let videoDeviceInput = self.videoDeviceInput,
              videoDeviceInput.device.hasTorch == true,
              videoDeviceInput.device.isTorchAvailable == true
        else { return }
        do {
            try videoDeviceInput.device.lockForConfiguration()
            videoDeviceInput.device.torchMode = mode
            videoDeviceInput.device.unlockForConfiguration()
        } catch {
            print("Could not lock for configuration: \(error)")
        }
    }

torchModeには点灯の.onと消灯の.offを設定して使います。

        switchTorch(.on)

周りの明るさに応じて自動で調整する.autoがあるようですが、.autoがどれくらい使いやすいかは試したことがないので知っている方教えてください。

読み取り範囲の制限

読み取り範囲の制限です。

カメラPreviewの中のある枠の中だけQRコードを読み取りたいという要望はあると思います。
例えば下図の赤枠の中に入ったQRコードだけを読み取りたいといった場合です。

赤枠

それを実現するのがAVCaptureMetadataOutputrectOfInterestプロパティです。rectOfInterestは四角を表すCGRect\で、このrectOfInterest`が指定する領域でないメタデータは検知されません。

例えば赤い枠を表すdetectAreaというViewを作りその枠内のQRコードだけを検知するようにしてみましょう。

まずdetectAreaViewを準備して赤枠をつけます。

    @IBOutlet private weak var detectArea: UIView! {
        didSet {
            detectArea.layer.borderWidth = 3.0
            detectArea.layer.borderColor = UIColor.red.cgColor
        }
    }

metadataOutput.rectOfInterestにそのViewの領域をセットしたいのですが、metadataOutputの世界とUIKitの世界の座標系は異なります。

それを変換するメソッドがpreviewLayer.metadataOutputRectConverted(fromLayerRect: )です。これを用いてUIKitの座標系からmetadataOutputの座標系に変換してrectOfInterestにセットすればOKです。

それぞれ実行すべきスレッドが異なるので気をつけてください。

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
+        sessionQueue.async {
+            DispatchQueue.main.async {

+                 print(self.detectArea.frame)
+                //(95.0, 322.0, 200.0, 200.0)
+
+                let metadataOutputRectOfInterest = self.previewLayer.metadataOutputRectConverted(fromLayerRect: self.detectArea.frame)
+                
+                print(metadataOutputRectOfInterest)
+                //(0.35937499999999994, 0.32539682539682546, 0.22321428571428575, 0.39682539682539686)
+
+                self.sessionQueue.async {
+                    self.metadataOutput.rectOfInterest = metadataOutputRectOfInterest
+                }
+            }
            
            self.session.startRunning()
        }
    }

QRコードの四隅(corners)を取得する

読み取ったQRコードの四隅を取得することもできます。

AVMetadataMachineReadableCodeObjectはQRコードなどの四隅を表すプロパティcornerを持っています。

先の例と同じくmetadataOutputの世界とUIKitの世界の座標系が異なるので、previewLayer.transformedMetadataObject(for: )でUIKitの座標系に変換してあげます。

あとはそれらの座標を使って何かを描画する処理をすればOKです。
UI関連の処理はmainスレッドで行うのを忘れずに。

extension ViewController: AVCaptureMetadataOutputObjectsDelegate {
    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {

        if let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject {
            
            print(metadataObject.corners)
            //[(0.38234764446543834, 0.524937247412264), (0.5244208584882264, 0.5242207337672014), (0.5423788338963728, 0.29560738616712384), (0.39114516054077575, 0.2896617102521232)]
            guard let transformedObject = previewLayer.transformedMetadataObject(for: metadataObject) as? AVMetadataMachineReadableCodeObject else {
                return
            }
            
            print(transformedObject.corners)
            //[(194.43162730421892, 342.5834894410327), (194.79275018133058, 469.88108920545085), (310.0138773717696, 485.97143517115), (313.01049803292995, 350.4660638445351)]
            
            DispatchQueue.main.async {
                // transformedObject.cornerを使って何か描画する処理
            }
        }
    }
}

こんな感じになりました!

e.g. QRコードの枠線を検知して緑のラインを描画した例
Bounding Box

これを用いると例えば

  • Previewに表示されているQRコードに枠線を表示してわかりやすくしたり
  • QRコードを読み込んだときにQRコードの位置を追跡するようなアニメーションを表示したり
  • QRコードの周辺に別のViewを表示してタップできるようにしたり

といろいろ応用することができますね。

音によるフィードバック

直接QRコード読み取りに関連する機能ではないですが、QRコードを読み取った感を醸成するためにサウンドも鳴らしてみましょう!

オリジナルの音源でも良いですが、ここではお手軽にSystemSoundを鳴らしてみました。
SystemSoundIDを指定してAudioServicesPlaySystemSound()を使ってみます。

    private func playSuccessSound() {
        let soundIdRing: SystemSoundID = 1057
        AudioServicesPlaySystemSound(soundIdRing)
    }

    private func playErrorSound() {
        let soundIdError: SystemSoundID = 1073
        AudioServicesPlayAlertSound(soundIdError)
    }

振動によるフィードバック

振動=HapticfeedbackでもQRコードを読み込んだ感を醸成しましょう!

音はオフにされてしまう場合も多いのでこちらだけでもきちっと仕込んどくと良いでしょう。
ここではFeedbackTypeのsuccessとerrorをそのまま設定しています。

    private func HapticSuccessNotification() {
        let g = UINotificationFeedbackGenerator()
        g.prepare()
        g.notificationOccurred(.success)
    }
    
    private func HapticErrorNotification() {
        let g = UINotificationFeedbackGenerator()
        g.prepare()
        g.notificationOccurred(.error)
    }

シーンに応じた使いやすいQRコード読み取り

さて、ここまででいろいろな材料を揃えてきました。
ようやく本題のシーンに応じた使いやすいQRコード読み取りについて考えていきましょう。

シーン①:シェアサイクル

シーン①:シェアサイクル

まずはシーン①としてシェアサイクルを想像してみましょう。
具体的なシチュエーションを思い浮かべると楽しいと思うのでぜひみなさん想像を頑張ってみてください。

シェアサイクルは自転車の鍵に表示されたQRコードをアプリから読み込んで解錠して使うことが多いですね。

読み取る対象のQRは1つ。

読み取る場所は屋外です。昼間の通りだったり、屋内の駐輪場だったり、夜真っ暗な時間帯で使われたりします。

トーチの点灯機能は大事です。お客さま自身でトーチをON/OFFする機能はもちろん、暗さを自動判定して自動点灯するといいかもしれません。
AVCaptureDeviceのtorchModeの.auto設定が使い物になればそれで良し。
私は自力でカメラから入力した画像の明るさ(Brightness)を取得して自動判定に使ったりしています。

また、自転車のQRコードを読み取るときはスマホとQRコードがちょっと離れた状態で読み取ることが多いかもしれません。場合によっては最初からZoomしておくと良いかもですね

ちょっと離れた場所から読み取る場合は画面のど真ん中にあるのがちょうど良いです。

このあたりの微調整は自転車のQRコードを何度も読み取ってみて自身で試してみると良いでしょう。

シーン②:在庫管理システム

シーン②:在庫管理システム

シーン②は在庫管理システムです。
例えばビールの樽を管理するアプリを想像してみましょう。

たくさんの樽を出荷するために出荷対象の樽を連続で読んでいきます。
なのでPreview画面を表示したまま、読み込み結果がわかるViewなどを同時に表示すると良いでしょう。

だいたいが薄暗い倉庫の中での作業になるのでQRスキャン時のTorchは常時点灯で良いでしょう

迅速に連続で読み取るには音と振動によるフィードバックが重要です。
視覚では遅いです。正常系と異常系が音と振動でわかるようにするといいですね。
それぞれ短めでわかりやすいものを設置すると良いと思います。

読み取り範囲の制限も重要です。
複数のQRコードが近くにある場合、画面の端に映ったQRコードを誤爆することがあります。
なので作業者が狙った範囲のQRだけ読み取りやすいよう、明確な読み取り範囲を設定すると良いですね!

シーン③:コード決済

シーン③:コード決済

シーン③はQRコードによる決済です。
お買い物をした店舗側がQRコードを提示して、お客さまがそれを読み込んで決済する方式ですね。

バーなどの暗い店舗に備えてトーチの手動ON/OFFは備えておく必要はありますが、
コード決済の現場はだいたい屋内の明るい店舗で、読み込む対象も1つなので読み取り困難な場合は少なそうです。

安定した読み取り機能はもちろんのこと、QRコードを読みことが楽しい体験につながるようなアニメーションやサウンドを加えるのが良さそうです

小さな特徴としてかなり近くでQRコードを読むことが多いと思われます。
その場合、QRコードを画面のやや上部で捉えるようにガイドを設定しておくとお客さまがスマホをかざしたときに自然に読み取れる位置になりやすそうです。

まとめ

このセッションでは

  • 基本的なQRコードの読み取り方を学び、
  • QRコード読み取りでよくある周辺機能を学び
  • シーンに応じてそれらの機能を組み合わせていく

ことを紹介しました。

どれも難しいことはしておらず、細かい事の微調整で驚くほど使いやすくなるのでぜひ手元で動かしてみてください。

例えば「複数のQRコードを同時に読み取った上で1つを選んで読み取りたい」とか他にも様々なシーンのQR読み取りを開発している方もいらっしゃると思います。他にもこんなQR読み取りの工夫をしているよ!という方ぜひ教えてください!

おまけ

AFFoundationを使った例を中心に紹介しましたが、Visionを使う場合も基本はほぼ同じです。

iOS16から使えるとても便利そうなDataScannerViewControllerももうすぐ使えるようになります。

今回お伝えした知識はQRだけでなく、何かを読み込むときにいろいろ応用できると思うので楽しみながら学んでいきましょう!

c.f. Capture machine-readable codes and text with VisionKit

Special Thanks

最後にSpecial Thanksを紹介させてください。
本セッションは3社の異なるアプリを触った経験から生まれました。

Charichari : neuet株式会社

まずはneuet株式会社。
「まちの移動の、つぎの習慣をつくる」というミッションを掲げて、シェサイクルサービスのCharichariを展開しています。

Charichari : neuet株式会社

QRコードは自転車の鍵を開けるときに利用しています。

樽管理 : Best Beer Japan株式会社

次にBest Beer Japan株式会社。
「ビールで人生にフレーバーを」というミッションを掲げて、クラフトビールに特化したITと物流サービスを展開しています。

樽管理 : Best Beer Japan株式会社

QRコードはビールの樽の管理に利用しています。

メルペイ(メルカリ) : 株式会社メルペイ

最後に株式会社メルペイ。
「信用を創造して、なめらかな社会を創る」というミッションを掲げて、決済サービスを展開しています。

メルペイ(メルカリ) : 株式会社メルペイ

QRコードはQRコード決済に利用しています。

参考資料

参考資料はこちらです。

QRコードそのものについては「QRコードドットコム」というWebサイトや「QRコードの奇跡」という書籍がおもしろいです。

カメラアプリとバーコード読み取りのサンプルはAppleの公式のものです。今回取り上げなかった端末の向きを変えたときやセッションのストップ・再開などについても実装されているので、実際のProductで使いたい場合は必見です。

カメラを用いて何かを読み取る系の進化についてはWWDCのセッションを見ていくと良いでしょう。

最後に私が作ったサンプルアプリを置いておきます。サクッと動作を試してみたい場合などにどうぞ。

Thank you!

ありがとうございました!

Discussion