You can find another native iOS solution using Swift 4 and Xcode 9 at below. Native AVFoundation
framework used with in this solution.
First part is the a subclass of UIViewController
which have related setup and handler functions for AVCaptureSession
.
import UIKit import AVFoundation class BarCodeScannerViewController: UIViewController { let captureSession = AVCaptureSession() var videoPreviewLayer: AVCaptureVideoPreviewLayer! var initialized = false let barCodeTypes = [AVMetadataObject.ObjectType.upce, AVMetadataObject.ObjectType.code39, AVMetadataObject.ObjectType.code39Mod43, AVMetadataObject.ObjectType.code93, AVMetadataObject.ObjectType.code128, AVMetadataObject.ObjectType.ean8, AVMetadataObject.ObjectType.ean13, AVMetadataObject.ObjectType.aztec, AVMetadataObject.ObjectType.pdf417, AVMetadataObject.ObjectType.itf14, AVMetadataObject.ObjectType.dataMatrix, AVMetadataObject.ObjectType.interleaved2of5, AVMetadataObject.ObjectType.qr] override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) setupCapture() // set observer for UIApplicationWillEnterForeground, so we know when to start the capture session again NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: .UIApplicationWillEnterForeground, object: nil) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // this view is no longer topmost in the app, so we don't need a callback if we return to the app. NotificationCenter.default.removeObserver(self, name: .UIApplicationWillEnterForeground, object: nil) } // This is called when we return from another app to the scanner view @objc func willEnterForeground() { setupCapture() } func setupCapture() { var success = false var accessDenied = false var accessRequested = false let authorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) if authorizationStatus == .notDetermined { // permission dialog not yet presented, request authorization accessRequested = true AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted:Bool) -> Void in self.setupCapture(); }) return } if authorizationStatus == .restricted || authorizationStatus == .denied { accessDenied = true } if initialized { success = true } else { let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInTelephotoCamera, .builtInDualCamera], mediaType: .video, position: .unspecified) if let captureDevice = deviceDiscoverySession.devices.first { do { let videoInput = try AVCaptureDeviceInput(device: captureDevice) captureSession.addInput(videoInput) success = true } catch { NSLog("Cannot construct capture device input") } } else { NSLog("Cannot get capture device") } } if success { DispatchQueue.global().async { self.captureSession.startRunning() DispatchQueue.main.async { let captureMetadataOutput = AVCaptureMetadataOutput() self.captureSession.addOutput(captureMetadataOutput) let newSerialQueue = DispatchQueue(label: "barCodeScannerQueue") // in iOS 11 you can use main queue captureMetadataOutput.setMetadataObjectsDelegate(self, queue: newSerialQueue) captureMetadataOutput.metadataObjectTypes = self.barCodeTypes self.videoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession) self.videoPreviewLayer.videoGravity = .resizeAspectFill self.videoPreviewLayer.frame = self.view.layer.bounds self.view.layer.addSublayer(self.videoPreviewLayer) } } initialized = true } else { // Only show a dialog if we have not just asked the user for permission to use the camera. Asking permission // sends its own dialog to th user if !accessRequested { // Generic message if we cannot figure out why we cannot establish a camera session var message = "Cannot access camera to scan bar codes" #if (arch(i386) || arch(x86_64)) && (!os(macOS)) message = "You are running on the simulator, which does not hae a camera device. Try this on a real iOS device." #endif if accessDenied { message = "You have denied this app permission to access to the camera. Please go to settings and enable camera access permission to be able to scan bar codes" } let alertPrompt = UIAlertController(title: "Cannot access camera", message: message, preferredStyle: .alert) let confirmAction = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in self.navigationController?.popViewController(animated: true) }) alertPrompt.addAction(confirmAction) self.present(alertPrompt, animated: true, completion: nil) } } } func handleCapturedOutput(metadataObjects: [AVMetadataObject]) { if metadataObjects.count == 0 { return } guard let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject else { return } if barCodeTypes.contains(metadataObject.type) { if let metaDataString = metadataObject.stringValue { captureSession.stopRunning() displayResult(code: metaDataString) return } } } func displayResult(code: String) { let alertPrompt = UIAlertController(title: "Bar code detected", message: code, preferredStyle: .alert) if let url = URL(string: code) { let confirmAction = UIAlertAction(title: "Launch URL", style: .default, handler: { (action) -> Void in UIApplication.shared.open(url, options: [:], completionHandler: { (result) in if result { NSLog("opened url") } else { let alertPrompt = UIAlertController(title: "Cannot open url", message: nil, preferredStyle: .alert) let confirmAction = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in }) alertPrompt.addAction(confirmAction) self.present(alertPrompt, animated: true, completion: { self.setupCapture() }) } }) }) alertPrompt.addAction(confirmAction) } let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) -> Void in self.setupCapture() }) alertPrompt.addAction(cancelAction) present(alertPrompt, animated: true, completion: nil) } }
Second part is the extension of our UIViewController
subclass for AVCaptureMetadataOutputObjectsDelegate
where we catch the captured outputs.
extension BarCodeScannerViewController: AVCaptureMetadataOutputObjectsDelegate { func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { handleCapturedOutput(metadataObjects: metadataObjects) } }
Update for Swift 4.2
.UIApplicationWillEnterForeground
changes as UIApplication.willEnterForegroundNotification
.