Last active
August 21, 2019 10:50
-
-
Save akhtarraza/490e4f247f2fa64d25af3f168152df39 to your computer and use it in GitHub Desktop.
QR Scanner view implemented with delegates
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import AVFoundation | |
enum QRScannerError: Error { | |
case deviceNotSupported | |
case permissionError(Error?) | |
case noQrFound | |
} | |
/// Delegate callback for the QRScannerView. | |
protocol QRScannerViewDelegate: class { | |
func qrScanningDidFail(with error:QRScannerError) | |
func qrScanningSucceeded(with qrString: String) | |
func qrScanningDidStop() | |
} | |
class QRScannerView: UIView { | |
weak var delegate: QRScannerViewDelegate? { | |
didSet { | |
if scannerAvailable == false { | |
delegate!.qrScanningDidFail(with: .deviceNotSupported) | |
} | |
} | |
} | |
var scannerAvailable: Bool { | |
let devices = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDuoCamera, .builtInTelephotoCamera, .builtInWideAngleCamera], mediaType: .video, position: .back).devices | |
return devices.first != nil | |
} | |
/// capture settion which allows us to start and stop scanning. | |
var captureSession: AVCaptureSession? | |
// Init methods.. | |
required init?(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
if scannerAvailable { | |
doInitialSetup() | |
} | |
} | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
if scannerAvailable { | |
doInitialSetup() | |
} | |
} | |
//MARK: overriding the layerClass to return `AVCaptureVideoPreviewLayer`. | |
override class var layerClass: AnyClass { | |
return AVCaptureVideoPreviewLayer.self | |
} | |
override var layer: AVCaptureVideoPreviewLayer { | |
return super.layer as! AVCaptureVideoPreviewLayer | |
} | |
} | |
extension QRScannerView { | |
public var isRunning: Bool { | |
return captureSession?.isRunning ?? false | |
} | |
public func startScanning() { | |
AVCaptureDevice.requestAccess(for: .video) { [weak self] success in | |
if success { | |
self?.captureSession?.startRunning() | |
} else { | |
self?.delegate?.qrScanningDidFail(with: .permissionError(nil)) | |
} | |
} | |
} | |
public func stopScanning() { | |
captureSession?.stopRunning() | |
delegate?.qrScanningDidStop() | |
} | |
/// Does the initial setup for captureSession | |
private func doInitialSetup() { | |
clipsToBounds = true | |
captureSession = AVCaptureSession() | |
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return } | |
let videoInput: AVCaptureDeviceInput | |
do { | |
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice) | |
} catch let error { | |
print(error) | |
delegate?.qrScanningDidFail(with: .permissionError(error)) | |
return | |
} | |
if (captureSession?.canAddInput(videoInput) ?? false) { | |
captureSession?.addInput(videoInput) | |
} else { | |
scanningDidFail() | |
return | |
} | |
let metadataOutput = AVCaptureMetadataOutput() | |
if (captureSession?.canAddOutput(metadataOutput) ?? false) { | |
captureSession?.addOutput(metadataOutput) | |
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) | |
metadataOutput.metadataObjectTypes = [.qr, .ean8, .ean13, .pdf417] | |
} else { | |
scanningDidFail() | |
return | |
} | |
self.layer.session = captureSession | |
self.layer.videoGravity = .resizeAspectFill | |
captureSession?.startRunning() | |
} | |
func scanningDidFail() { | |
delegate?.qrScanningDidFail(with: .noQrFound) | |
captureSession = nil | |
} | |
func found(code: String) { | |
delegate?.qrScanningSucceeded(with: code) | |
} | |
} | |
extension WFQRScannerView: AVCaptureMetadataOutputObjectsDelegate { | |
func metadataOutput(_ output: AVCaptureMetadataOutput, | |
didOutput metadataObjects: [AVMetadataObject], | |
from connection: AVCaptureConnection) { | |
stopScanning() | |
if let metadataObject = metadataObjects.first { | |
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return } | |
guard let stringValue = readableObject.stringValue else { return } | |
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) | |
found(code: stringValue) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment