187 lines
6.9 KiB
Swift
187 lines
6.9 KiB
Swift
//
|
|
// QRCodeScanViewController.swift
|
|
// BFL Authenticator
|
|
//
|
|
// Created by Jakub Dolejs on 28/03/2018.
|
|
// Copyright © 2018 Applied Recognition Inc. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import AVFoundation
|
|
|
|
public class QRCodeScanViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
|
|
|
|
public weak var delegate: QRCodeScanViewControllerDelegate?
|
|
public var prompt: String?
|
|
|
|
public static func create() -> QRCodeScanViewController {
|
|
let storyboard = UIStoryboard(name: "QRCode", bundle: Bundle(for: QRCodeScanViewController.self))
|
|
return storyboard.instantiateInitialViewController() as! QRCodeScanViewController
|
|
}
|
|
|
|
private override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
|
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
}
|
|
|
|
@IBOutlet var cameraView: UIView!
|
|
@IBOutlet var promptLabel: UILabel!
|
|
var captureSession: AVCaptureSession!
|
|
lazy var barcodeScanQueue: DispatchQueue = {
|
|
let queue = DispatchQueue.init(label: "com.appliedrec.qrcodescan")
|
|
return queue
|
|
}()
|
|
var avCaptureVideoOrientation: AVCaptureVideoOrientation {
|
|
switch UIApplication.shared.statusBarOrientation {
|
|
case .portraitUpsideDown:
|
|
return AVCaptureVideoOrientation.portraitUpsideDown
|
|
case .landscapeRight:
|
|
return AVCaptureVideoOrientation.landscapeRight
|
|
case .landscapeLeft:
|
|
return AVCaptureVideoOrientation.landscapeLeft
|
|
default:
|
|
return AVCaptureVideoOrientation.portrait
|
|
}
|
|
}
|
|
|
|
public override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
if let prompt = self.prompt {
|
|
self.promptLabel.text = prompt
|
|
}
|
|
}
|
|
|
|
public override func didReceiveMemoryWarning() {
|
|
super.didReceiveMemoryWarning()
|
|
// Dispose of any resources that can be recreated.
|
|
}
|
|
|
|
public override func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
startCamera()
|
|
}
|
|
|
|
func startCamera() {
|
|
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
|
switch status {
|
|
case .authorized:
|
|
self.startScan()
|
|
case .notDetermined:
|
|
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
if granted {
|
|
self.startScan()
|
|
} else {
|
|
self.showCameraAccessDenied()
|
|
}
|
|
}
|
|
case .denied:
|
|
self.showCameraAccessDenied()
|
|
case .restricted:
|
|
self.showCameraAccessRestricted()
|
|
@unknown default:
|
|
fatalError()
|
|
}
|
|
}
|
|
|
|
public override func viewWillDisappear(_ animated: Bool) {
|
|
super.viewWillDisappear(animated)
|
|
self.stopCamera()
|
|
}
|
|
|
|
@IBAction func restartQRCodeScan(_ segue: UIStoryboardSegue) {
|
|
|
|
}
|
|
|
|
func showCameraAccessDenied() {
|
|
DispatchQueue.main.async {
|
|
self.performSegue(withIdentifier: "denied", sender: nil)
|
|
}
|
|
}
|
|
|
|
func showCameraAccessRestricted() {
|
|
DispatchQueue.main.async {
|
|
self.performSegue(withIdentifier: "restricted", sender: nil)
|
|
}
|
|
}
|
|
|
|
private func startScan() {
|
|
self.barcodeScanQueue.async {
|
|
self.captureSession = AVCaptureSession()
|
|
guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
|
|
return
|
|
}
|
|
guard let videoInput = try? AVCaptureDeviceInput(device: camera) else {
|
|
return
|
|
}
|
|
do {
|
|
try camera.lockForConfiguration()
|
|
if camera.isAutoFocusRangeRestrictionSupported {
|
|
camera.autoFocusRangeRestriction = .near
|
|
}
|
|
if camera.isFocusModeSupported(AVCaptureDevice.FocusMode.continuousAutoFocus) {
|
|
camera.focusMode = .continuousAutoFocus
|
|
} else if camera.isFocusModeSupported(AVCaptureDevice.FocusMode.autoFocus) {
|
|
camera.focusMode = .autoFocus
|
|
}
|
|
if camera.isFocusPointOfInterestSupported {
|
|
camera.focusPointOfInterest = CGPoint(x: 0.5, y: 0.5)
|
|
}
|
|
camera.unlockForConfiguration()
|
|
} catch {
|
|
|
|
}
|
|
if self.captureSession.canAddInput(videoInput) {
|
|
self.captureSession.addInput(videoInput)
|
|
}
|
|
let barcodeOutput = AVCaptureMetadataOutput()
|
|
barcodeOutput.setMetadataObjectsDelegate(self, queue: self.barcodeScanQueue)
|
|
if self.captureSession.canAddOutput(barcodeOutput) {
|
|
self.captureSession.addOutput(barcodeOutput)
|
|
if barcodeOutput.availableMetadataObjectTypes.contains(where: { (val) -> Bool in return val == AVMetadataObject.ObjectType.qr }) {
|
|
barcodeOutput.metadataObjectTypes = [.qr]
|
|
}
|
|
}
|
|
self.captureSession.startRunning()
|
|
DispatchQueue.main.async {
|
|
let previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
|
|
previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
|
previewLayer.frame = self.cameraView.bounds
|
|
if previewLayer.connection != nil && previewLayer.connection!.isVideoOrientationSupported {
|
|
previewLayer.connection!.videoOrientation = self.avCaptureVideoOrientation
|
|
}
|
|
self.cameraView.layer.masksToBounds = true
|
|
while let sub = self.cameraView.layer.sublayers?.first {
|
|
sub.removeFromSuperlayer()
|
|
}
|
|
self.cameraView.layer.addSublayer(previewLayer)
|
|
}
|
|
}
|
|
}
|
|
|
|
func stopCamera() {
|
|
self.barcodeScanQueue.async {
|
|
if self.captureSession != nil {
|
|
self.captureSession.stopRunning()
|
|
self.captureSession = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
public func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
|
|
if let codeValue = (metadataObjects.first(where: { $0 is AVMetadataMachineReadableCodeObject && $0.type == AVMetadataObject.ObjectType.qr }) as? AVMetadataMachineReadableCodeObject)?.stringValue {
|
|
self.stopCamera()
|
|
DispatchQueue.main.async {
|
|
self.delegate?.qrCodeScanViewController(self, didScanQRCode: codeValue)
|
|
self.delegate = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public protocol QRCodeScanViewControllerDelegate: class {
|
|
func qrCodeScanViewController(_ viewController: QRCodeScanViewController, didScanQRCode value: String)
|
|
}
|