Skip to content

Instantly share code, notes, and snippets.

@akhtarraza
Last active August 21, 2019 10:50
Show Gist options
  • Save akhtarraza/490e4f247f2fa64d25af3f168152df39 to your computer and use it in GitHub Desktop.
Save akhtarraza/490e4f247f2fa64d25af3f168152df39 to your computer and use it in GitHub Desktop.
QR Scanner view implemented with delegates
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