QRコードリーダー
AVFoundationを使います。
画像や音声を取得できるライブラリです。今回はmetadataを使ってバーコード認識のみ行います。
metadataObjectTypesで認識するバーコードを指定します。
バーコードを読み取ったらdidOutput metadataObjectsが呼ばれます。
rectOfInterestを指定してその範囲内のみ認識させるようにします。分かるように画面上に赤枠を出しますが、rectOfInterestは0.0~1.0の割合で指定する必要があるのでややこしい。今回は赤枠の位置を先に決めてから割合を計算しました。
ここがハマリポイントですが、デフォルトが水平になっているようなので、計算したあと縦横を入れ替える必要があります。
class BarcodeReaderViewController: UIViewController { var captureSession = AVCaptureSession() var currentDevice: AVCaptureDevice? var metadataOutput: AVCaptureMetadataOutput! var cameraPreviewLayer: AVCaptureVideoPreviewLayer! var outputString = "" var detectionArea: CGRect! var areaOfInterest: CGRect! // 0.0-1.0の範囲 var authStatus: AuthorizedStatus = .authorized enum AuthorizedStatus { case authorized case notAuthorized case failed } static func fromStoryboard() -> BarcodeReaderViewController? { return UIStoryboard(name: "Main", bundle: nil) .instantiateViewController(withIdentifier: "BarcodeReaderViewController") as? BarcodeReaderViewController } override func viewDidLoad() { super.viewDidLoad() setupCloseButton() setupMessage() cameraAuth() if authStatus != .authorized { dismiss(animated: true) return } setupDetectionArea() setupDevice() setupInputOutput() setupPreviewLayer() DispatchQueue.global(qos: .userInitiated).async { self.captureSession.startRunning() } } // 認識エリアの計算 func setupDetectionArea() { // まず赤枠のサイズを決める(横幅の70%のサイズの正方形を上から30%の位置に表示) detectionArea = CGRect(x: view.bounds.width * 0.15, y: view.bounds.height * 0.3, width: view.bounds.width * 0.7, height: view.bounds.width * 0.7) // 画面の大きさで割って割合を計算 let interest = CGRect(x: detectionArea.origin.x / view.bounds.width, y: detectionArea.origin.y / view.bounds.height, width: detectionArea.width / view.bounds.width, height: detectionArea.height / view.bounds.height) // 縦横を入れ替える areaOfInterest = CGRect(x: interest.origin.y, y: interest.origin.x, width: interest.height, height: interest.width) // 縦横入れ替え } // カメラの準備 func setupDevice() { // 通常広角カメラの背面カメラを探す let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back) let devices = deviceDiscoverySession.devices for device in devices { currentDevice = device break } } // 入力と出力の設定 func setupInputOutput() { guard let device = currentDevice else { print("No camera available") return } do { // 入力 let captureDeviceInput = try AVCaptureDeviceInput(device: device) captureSession.addInput(captureDeviceInput) // 出力 metadataOutput = AVCaptureMetadataOutput() metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) captureSession.addOutput(metadataOutput) // QRコードを認識します。addOutputより後ろで設定すること metadataOutput.metadataObjectTypes = [.qr] // 認識エリアを指定する metadataOutput.rectOfInterest = areaOfInterest } catch { print(error) } } // バーコード探索中のプレビュー表示 func setupPreviewLayer() { // 背面全部を映す cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) cameraPreviewLayer.videoGravity = .resizeAspectFill cameraPreviewLayer.connection?.videoOrientation = .portrait cameraPreviewLayer.frame = view.bounds view.layer.insertSublayer(cameraPreviewLayer, at: 0) // 認識エリアに赤枠を出す let borderView = UIView(frame: detectionArea) borderView.layer.borderWidth = 2 borderView.layer.borderColor = UIColor.red.cgColor view.addSubview(borderView) } func foundBarcode() { captureSession.stopRunning() self.dismiss(animated: true) } } // MARK: - AVCaptureMetadataOutputObjectsDelegate extension BarcodeReaderViewController: AVCaptureMetadataOutputObjectsDelegate { func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { for metadataObject in metadataObjects { if let metadata = metadataObject as? AVMetadataMachineReadableCodeObject { if metadata.type == .qr { if let string = metadata.stringValue { // 文字列が取れたら画面を閉じます outputString = string self.foundBarcode() } } } } } }
他の部分のコード
extension BarcodeReaderViewController { func cameraAuth() { let status = AVCaptureDevice.authorizationStatus(for: .video) switch status { case .notDetermined: // 初回起動時、許可を求める AVCaptureDevice.requestAccess(for: .video) {[unowned self] authorized in print("初回", authorized.description) if authorized { self.authStatus = .authorized } else { self.authStatus = .notAuthorized } } case .restricted, .denied: authStatus = .notAuthorized case .authorized: authStatus = .authorized @unknown default: fatalError() } } func setupCloseButton() { let button = UIButton() button.setTitle("✗", for: .normal) button.tintColor = UIColor.white button.backgroundColor = UIColor(white: 0.1, alpha: 0.8) button.layer.cornerRadius = 16 button.addTarget(self, action: #selector(tapClose(_:)), for: .touchUpInside) view.addSubview(button) button.translatesAutoresizingMaskIntoConstraints = false button.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16).isActive = true button.topAnchor.constraint(equalTo: view.topAnchor, constant: 32).isActive = true button.widthAnchor.constraint(equalToConstant: 32).isActive = true button.heightAnchor.constraint(equalToConstant: 32).isActive = true } @objc func tapClose(_ button: UIButton) { dismiss(animated: true) } func setupMessage() { let label = UILabel() label.text = "QRコードを読み取ってください" label.numberOfLines = 2 label.textColor = UIColor.white view.addSubview(label) label.translatesAutoresizingMaskIntoConstraints = false label.topAnchor.constraint(equalTo: view.topAnchor, constant: 128).isActive = true label.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 32).isActive = true label.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 32).isActive = true }