swift で iOS 用QRコードリーダーを作ってみる

[こちら](https://www.appcoda.com/barcode-reader-swift/)のサイトに従って書きました。
おおよそ従って書いていただけなので、そのままでは動かなかったことや注意点について書いていきます。

## 準備
1. 上記サイトに貼られている[こちら](https://github.com/appcoda/QRCodeReader/blob/master/QRCodeReaderStarter.zip)から枠組みをダウンロードして展開します。そこに含まれているファイルに書き加えていく形で実装していきます。
2. 展開したフォルダ直下にある `QRCodeReader.xcodeproj` をダブルクリックして開きます。Xcodeが起動するはずです。
3. 左ペインで一番上のプロジェクトファイルをクリックします。メインペインで `General` を選択すると下の画像のページが表示されるはずです。下の画像で青い四角で囲っている部分が `None` になっている場合は、他のチーム例えば `<自分の名前>（Personal Team）` にしておきます。
<img width="1346" alt="スクリーンショット.png" src="https://qiita-image-store.s3.amazonaws.com/0/181912/7e938a9a-17cc-3c30-faea-4cbf310b0c03.png">

3. 左ペインで `info.plist` をクリックしてメインペインに開きます。その中の `Privacy - Camera Usage Description` で `カメラアクセスが必要` を選択します。
3. `QRScannerController` をXcodeのエディタで開きます。

## 実装

`import UIKit`
のすぐ下に

```swift
import AVFoundation
```

を加える。

クラスにプロトコルを増やす。

```swift
class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate 
```

変数を宣言する。

```swift
let captureSession = AVCaptureSession()
    var videoPreviewLayer:AVCaptureVideoPreviewLayer?
    var qrCodeFrameView:UIView？
```

記事では変数3つとも `?` (optional)でしたが、うまく行かなかったので、始めから生成しました。

カメラを使える状態にする
これからしばらく `viewDidLoad()` の中に書いていきます。

```swift
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back)
 
guard let captureDevice = deviceDiscoverySession.devices.first else {
    print("Failed to get the camera device")
    return
}
 
do {
    let input = try AVCaptureDeviceInput(device: captureDevice)
    
    captureSession.addInput(input)
    
} catch {
    print(error)
    return
}
```

記事では `deviceTypes: [.builtInDualCamera]` で探していました。比較的最新の端末であればこれで良いですが、私のは iPhone 6S Plus でデュアルカメラではなかったので、代わりに `deviceTypes: [.builtInWideAngleCamera]` を使用しました。

`do` ブロックの中に書き加えていき、以下のようになります。

```swift
        do {      
            let input = try AVCaptureDeviceInput(device: captureDevice)
            
            captureSession.addInput(input)
            
            let captureMetadataOutput = AVCaptureMetadataOutput()
            captureSession.addOutput(captureMetadataOutput)
            
            captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            captureMetadataOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]
            
            videoPreviewLayer = AVCaptureVideoPreviewLayer.init(session: captureSession)
            videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
            videoPreviewLayer?.frame = view.layer.bounds
            view.layer.addSublayer(videoPreviewLayer!)

            self.captureSession.startRunning()
            view.bringSubview(toFront: messageLabel)
            view.bringSubview(toFront: topbar)

            qrCodeFrameView = UIView()
            // スキャンしたQRコードを緑の枠で囲む
            if let qrCodeFrameView = qrCodeFrameView {
                qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
                qrCodeFrameView.layer.borderWidth = 2
                view.addSubview(qrCodeFrameView)
                view.bringSubview(toFront: qrCodeFrameView)
            }
        }
```

最後にQRコードをデコードする。

```swift
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        // Check if the metadataObjects array is not nil and it contains at least one object.
        if metadataObjects.count == 0 {
            qrCodeFrameView?.frame = CGRect.zero
            messageLabel.text = "No QR code is detected"
            return
        }
        
        // Get the metadata object.
        let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
        
        if metadataObj.type == AVMetadataObject.ObjectType.qr {
            // If the found metadata is equal to the QR code metadata then update the status label's text and set the bounds
            let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
            qrCodeFrameView?.frame = barCodeObject!.bounds
            
            if metadataObj.stringValue != nil {
                messageLabel.text = metadataObj.stringValue
            }
        }
    }
```

## 実行する

Xcode左上の「iPhone 何々」と書かれている部分で何で実行するかを選択できます。接続しなくても選べるものはエミュレータです。
手持ちの iPhone を Macに接続すると そこで自分の端末を選択できるようになります。<img width="316" alt="スクリーンショット.png" src="https://qiita-image-store.s3.amazonaws.com/0/181912/e891fb75-b08a-5fde-d568-70bd2527cfdb.png">
左△の再生ボタンのような実行ボタンをクリックしてiPhoneで許可をすると手持ちのiPhone で実行できます。