3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【備忘録】iOSでQRコードを読み取る

Last updated at Posted at 2021-04-25

1. はじめに

こちらの記事を丸パクリさせていただきました。ありがとうございます。

少し変更を加えて備忘録としてここに残します。

2. info.plist

info.plistPrivacy - Camera Usage Descriptionを追加します。
値にはカメラを使う説明?を入れときます。自分は「カメラを使います。」といれときました。

3. MyQRCodeReader.swift

QRコード読み取りを司るクラスを作ります。

import AVFoundation
import UIKit
import Foundation

class MyQRCodeReader {
    
    let captureSession = AVCaptureSession()
    let videoDevice = AVCaptureDevice.default(for: AVMediaType.video)
    var metadataOutput = AVCaptureMetadataOutput()
    var delegate:AVCaptureMetadataOutputObjectsDelegate? {
        get{
            return self.forwardDelegate
        }
        set(v){
            self.forwardDelegate = v
            self.metadataOutput.setMetadataObjectsDelegate(v, queue: DispatchQueue.main)
        }
    }
    var forwardDelegate:AVCaptureMetadataOutputObjectsDelegate?
    var preview: UIView?
    var previewLayer = AVCaptureVideoPreviewLayer()
    let qrView = UIView()

    //info.plist Privacy - Camera Usage Description:String
    func setupCamera( view:UIView, borderWidth:Int = 1, borderColor:CGColor =  UIColor.red.cgColor ){
        
        self.preview = view
        
        //デバイスからの入力
        do {
            let videoInput = try AVCaptureDeviceInput(device: self.videoDevice!) as AVCaptureDeviceInput
            self.captureSession.addInput(videoInput)
        } catch let error as NSError {
            print(error)
        }
        //出力
        self.captureSession.addOutput(self.metadataOutput)
        
        //読み込み対象タイプ
        self.metadataOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]

        //カメラ映像を表示
        self.cameraPreview(view)
        
        //認識QRの確認表示
        self.targetCapture( borderWidth:borderWidth, borderColor: borderColor )
        
        // 読み取り開始
        self.captureSession.startRunning()

    }
    
    func restartCamera() {
        self.captureSession.startRunning()
        qrView.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
    }
    
    func stopCamera() {
        self.captureSession.stopRunning()
        qrView.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
    }
       
    private func cameraPreview( _ view:UIView ){
        //カメラ映像を画面に表示
        self.previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
        previewLayer.frame = view.bounds
        previewLayer.videoGravity = .resizeAspectFill
        if let orientation = self.convertUIOrientation2VideoOrientation(f: {return self.appOrientation()}) {
            previewLayer.connection!.videoOrientation = orientation
        }
        view.layer.addSublayer(previewLayer)
    }
    
    private func targetCapture(borderWidth:Int, borderColor:CGColor){
        self.qrView.layer.borderWidth = CGFloat(borderWidth)
        qrView.layer.borderColor = borderColor
        qrView.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
        if let v = self.preview {
            v.addSubview(qrView)
        }
    }
    
    //読み取り範囲の指定
    public func readRange( frame:CGRect = CGRect(x: 0.3, y: 0.3, width: 0.4, height: 0.4) ){
        
        self.metadataOutput.rectOfInterest = CGRect(x: frame.minY,y: 1-frame.minX-frame.size.width, width: frame.size.height,height: frame.size.width)
        
        let v = UIView()
        v.layer.borderWidth = 5
        v.layer.borderColor = UIColor.red.cgColor
        if let preview = self.preview {
            v.frame = CGRect(x: preview.frame.size.width * frame.minX, y:  preview.frame.size.height * frame.minY, width: preview.frame.size.width * frame.size.width, height: preview.frame.size.height * frame.size.height )
            preview.addSubview(v)
        }
    }

    func delegate(_ delegate:AVCaptureMetadataOutputObjectsDelegate){
        //オブジェクトを読み込んだ時のdelegate AVCaptureMetadataOutputObjectsDelegate.metadataOutput
        self.metadataOutput.setMetadataObjectsDelegate(delegate, queue: DispatchQueue.main)
    }
    
    func appOrientation() -> UIInterfaceOrientation {
        return UIApplication.shared.statusBarOrientation
    }

    // UIInterfaceOrientation -> AVCaptureVideoOrientationにConvert
    func convertUIOrientation2VideoOrientation(f: () -> UIInterfaceOrientation) -> AVCaptureVideoOrientation? {
        let v = f()
        switch v {
        case UIInterfaceOrientation.unknown:
                return nil
            default:
                return ([
                    UIInterfaceOrientation.portrait: AVCaptureVideoOrientation.portrait,
                    UIInterfaceOrientation.portraitUpsideDown: AVCaptureVideoOrientation.portraitUpsideDown,
                    UIInterfaceOrientation.landscapeLeft: AVCaptureVideoOrientation.landscapeLeft,
                    UIInterfaceOrientation.landscapeRight: AVCaptureVideoOrientation.landscapeRight
                ])[v]
        }
    }
}

先ほど紹介した記事をそのままコピペし、画面の回転を考慮するように変えました。また、カメラをストップしたりリスタートする関数も入れときました。

4. ViewCntroller.swift

QR読み取りを行うViewControllerでMyQRCodeReaderを使います。


import UIKit
import AVFoundation

class ViewController: UIViewController  {
    
    let myQRCodeReader = MyQRCodeReader()
    
    private var inputQrCodeID: String?
    var videoDataOutputEnable = false
    var isFirstAppear = true
    
    override func viewDidLoad() {
        super.viewDidLoad()
        videoDataOutputEnable = true
        myQRCodeReader.delegate = self
        myQRCodeReader.setupCamera(view:self.view)
        //読み込めるカメラ範囲
        myQRCodeReader.readRange()
    }
    
    // NavigationControllerを導入してると戻ってくるときに前の画面が残るのでリスタートさせる
    override func viewWillAppear(_ animated: Bool) {
        if (!isFirstAppear) {
            myQRCodeReader.restartCamera()
        }
        isFirstAppear = false
        videoDataOutputEnable = true
    }
    // カメラを止めたい時
    func stopCamera() {
        myQRCodeReader.stopCamera()
    }
}


extension ViewController: AVCaptureMetadataOutputObjectsDelegate{
    //対象を認識、読み込んだ時に呼ばれる
    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        if (!videoDataOutputEnable){ return }
        videoDataOutputEnable = false
        //一画面上に複数のQRがある場合、複数読み込むが今回は便宜的に先頭のオブジェクトを処理
        if let metadata = metadataObjects.first as? AVMetadataMachineReadableCodeObject{
            let barCode = myQRCodeReader.previewLayer.transformedMetadataObject(for: metadata) as! AVMetadataMachineReadableCodeObject
            //読み込んだQRを映像上で枠を囲む。ユーザへの通知。必要な時は記述しなくてよい。
            myQRCodeReader.qrView.frame = barCode.bounds
            //QRデータを表示
            if let str = metadata.stringValue {
                print(str)
                inputQrCodeID = str
                // 読み取り完了
            }
        }
    }
}

videoDataOutputEnableで読み取りの有無を管理しています。放っておくと一秒間に何回も読み取ってしまうので一回読み取ったら後の読み込みは破棄するようにしてます。こうすることで読み込みするとすぐに次の画面に遷移できます。

全ての読み取り結果を処理したい時はif (!videoDataOutputEnable){ return }を削除してください。

myQRCodeReader.setupCamera(view:self.view)で全画面にpreview画面を表示してますが、任意のViewに変えることができます。

今後は読み取り時にアニメーションをするようにしたいですね。

4. さいごに

AndroidだとZxingで簡単にできるんですが...ちょっとクセある感じです。

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?