Edited at

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

More than 1 year has passed since last update.

こちらのサイトに従って書きました。

おおよそ従って書いていただけなので、そのままでは動かなかったことや注意点について書いていきます。


準備


  1. 上記サイトに貼られているこちらから枠組みをダウンロードして展開します。そこに含まれているファイルに書き加えていく形で実装していきます。

  2. 展開したフォルダ直下にある QRCodeReader.xcodeproj をダブルクリックして開きます。Xcodeが起動するはずです。

  3. 左ペインで一番上のプロジェクトファイルをクリックします。メインペインで General を選択すると下の画像のページが表示されるはずです。下の画像で青い四角で囲っている部分が None になっている場合は、他のチーム例えば <自分の名前>(Personal Team) にしておきます。

    スクリーンショット.png


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


  5. QRScannerController をXcodeのエディタで開きます。



実装

import UIKit

のすぐ下に

import AVFoundation

を加える。

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

class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate 

変数を宣言する。

let captureSession = AVCaptureSession()

var videoPreviewLayer:AVCaptureVideoPreviewLayer?
var qrCodeFrameView:UIView

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

カメラを使える状態にする

これからしばらく viewDidLoad() の中に書いていきます。

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 ブロックの中に書き加えていき、以下のようになります。

        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コードをデコードする。

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に接続すると そこで自分の端末を選択できるようになります。スクリーンショット.png

左△の再生ボタンのような実行ボタンをクリックしてiPhoneで許可をすると手持ちのiPhone で実行できます。