1. はじめに
こちらの記事を丸パクリさせていただきました。ありがとうございます。
少し変更を加えて備忘録としてここに残します。
2. info.plist
info.plist
にPrivacy - 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で簡単にできるんですが...ちょっとクセある感じです。