【SwiftUI】QRコードを読み込みに使える便利なフレームワーク(MercariQRScanner/CodeScanner)

# 概要

iOSアプリでQRコードを使ったチェックインを実装してほしいとの仕様依頼がありました。当方、iOSアプリはおろかSwiftという言語を触るのも初めてのペーペーなのでAVFoundationを理解し1から実装するには時間がかかると判断し、フレームワークで実装しようという決断に至りました。(いずれ挑戦します)

調査する中で、初心者の私でも使用できたライブラリを二つ紹介いたします。

# 動作環境

|ツール|バージョン|
|---|---|
|Xcode|14.2 |
|iOS|16.2|
|Cocoa Pod|1.11.3|

## MercariQRScanner

フレームワーク名からも分かる通り、あのメルカリさんがOSS化しているフレームワークです。
なんと言ってもあのメルカリさんのアプリ内でも使用しているQRコードリーダーが使えるということが最大の魅力だと思います。読み込むQRコードへフォーカスするアニメーションがついていたり、枠の大きさやフラッシュ機能を簡単に変更できます。

<img src="https://storage.googleapis.com/prd-hatena-engineering-asset/20191206174201.gif" width="200">

https://engineering.mercari.com/blog/entry/2019-12-12-094129/

https://github.com/mercari/QRScanner

#### 基本的な使い方
正確にはSWiftUIにそのまま取り込むことはできないため、一度`UIViewControllerRepresentable`でラップしてあげる必要があります。

```diff_swift:ドキュメントに追加

import MercariQRScanner

+ struct QrCodeScanner: UIViewControllerRepresentable {
+ 
+     func makeUIViewController(context: Context) -> QrCodeScannerController {
+         return QrCodeScannerController()
+     }
+ 
+     func updateUIViewController(_ uiViewController: QrCodeScannerController, context: Context) {}
+ }

final class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let qrScannerView = QRScannerView(frame: view.bounds)
        view.addSubview(qrScannerView)
        qrScannerView.configure(delegate: self)
        qrScannerView.startRunning()
    }
}

extension ViewController: QRScannerViewDelegate {
    func qrScannerView(_ qrScannerView: QRScannerView, didFailure error: QRScannerError) {
        print(error)
    }

    func qrScannerView(_ qrScannerView: QRScannerView, didSuccess code: String) {
        print(code)
    }
}
```

#### 文字列の取得/処理の実行

QRコードで読み込んだ文字列をView側で表示させたい場合にはView側で表示させたい変数を`@Binding`し、Delegateの中でその値を変化させることでView側の文字列が変更できます。
また、QRコードを読み込んで何か処理を行いたい場合は、`onChange`関数で変更を検知し処理を行えます。(読み込んだQRコードの文字列を使用してAPIを叩くなど)

```diff_swift
struct qrView: View {
+   @State private var qrCodeStr: String = ""

    var body: some View {
        Text(qrCodeStr)
+       QrCodeScanner(qrCodeStr: $qrCodeStr)
            .onChange(of: qrCodeStr) { _ in
                // 文字列に変更があったときに処理を走らせたい場合はここに記載
            }
    }
}

struct QrCodeScanner: UIViewControllerRepresentable {
+    @Binding var qrCodeStr: String

    func makeUIViewController(context: Context) -> QrCodeScannerController {
        let viewController = QrCodeScannerController()
+       viewController.qrCodeStr = $qrCodeStr
        return viewController
    }

    func updateUIViewController(_ uiViewController: QrCodeScannerController, context: Context) {}
}

final class ViewController: UIViewController {
+   var qrCodeStr: Binding<String>?

    override func viewDidLoad() {
        ...
    }

}

extension ViewController: QRScannerViewDelegate {
    func qrScannerView(_ qrScannerView: QRScannerView, didFailure error: QRScannerError) {
        print(error)
    }

    func qrScannerView(_ qrScannerView: QRScannerView, didSuccess code: String) {
-        print(code)
+        qrCodeStr?.wrappedValue = code
    }
}
```


今回はこちらのフレームワークを使用させていただきました。

## QRCodeReader

次に紹介するには、twostrawssさんが出しているフレームワーク`CodeScanner`です。

https://github.com/twostraws/CodeScanner

こちらのTutorialでも紹介されており非常に参考になるので確認してみてください。

https://www.hackingwithswift.com/books/ios-swiftui/scanning-qr-codes-with-swiftui

こちらはSwiftUIにそのまま組み込むことが可能で、取り入れるのはすごい簡単です。以下のViewを`.sheet`などで表示させてあげることでカメラが起動し文字列を読み取ることができます。

```swift:Usage
CodeScannerView(codeTypes: [.qr], simulatedData: "Paul Hudson") { response in                    
    switch response {
    case .success(let result):
        print("Found code: \(result.string)")
    case .failure(let error):
        print(error.localizedDescription)
    }
}
```

非常に簡単なので、最初はこちらのフレームワークを使用する予定だったのですが、MercariQRScannerとは違い読み取り方式が`.video`ではなく、`.photo`になっています。つまり、**QRコードが画面内に入るとカメラのシャッター音がなります。** 画像は保存されませんがQRコードを読み込むたびにシャッター音が鳴るのはユーザビリティ的にはNGでしたので使用は断念しました。(どこかの設定で変えれるかは不明...)

## 総評

色々なフレームワークを探している中で、メルカリさんがOSSでフレームワークを出していることに驚きました。自分はただフレームワークを使用してQRコードスキャナーを実装しただけですが、実機で検証しているときにQRコードをフォーカスする動作が美しく何回も読み込み動作を行なって悦に浸ってました。
フレームワークの製作者の皆様方に感謝すると共に、SwiftUIで標準に用意されている`AVFoundation`での実装に挑戦してよりSwiftの理解を深めたいなと思いました。

#### おまけ

初めてXcodeでコードを書いていますが、VSCodeで普段コードを書いている身からすると当たり前のようにフォーマッターやLintなどが効いているので整形前のコードは汚いです。(実力不足)
Xcodeにはおそらく標準ではインデントの修正ぐらいしかしてくれませんので、拡張機能を以下を参考にして入れました。触れたことがない方は入れてみることをお勧めします。

https://qiita.com/croquette0212/items/ccf023083bf6432bf1df

> ちなみにインデントの修正だけでいいという人は`Control + i`でできます。