QR Scanner in Swift

Payal Kandlur
3 min readSep 12, 2022

--

Almost every app now has QR be it authentication, more info and much more. Today let's dive right in and check how we can add a QR scanner to our iOS app.

Do not forget to add the camera permissions in your info.plist.

In your storyboard, add the UI you need, so this is how we have it, a camera view, with a scanner view (which scans our QR), and a button to turn on the flashlight.

Create a camera capture session, for which we need to use the AVFoundation framework which lets us work with audiovisual media.

In your View Controller import this framework.

import AVFoundation

Now we to implement the AVCaptureMetadataOutputObjectsDelegate protocol.

extension ViewController: AVCaptureMetadataOutputObjectsDelegate {}

AVFoundation has AVCaptureSession which provides the camera interface which is needed to scan the code.

func createCaptureSession() throws -> AVCaptureSession {captureSession = AVCaptureSession()guard let captureDevice = AVCaptureDevice.default(for: .video) else {print("Device error")throw QRScannerError.videoNotSupported}guard let videoInput = try? AVCaptureDeviceInput(device: captureDevice) else {throw QRScannerError.unableToCrateCaptureDeviceInput}print(videoInput)guard captureSession.canAddInput(videoInput) else {throw QRScannerError.cannotAddSessionCaptureDeviceInput}captureSession.addInput(videoInput)let metadataOutput = AVCaptureMetadataOutput()guard captureSession.canAddOutput(metadataOutput) else {throw QRScannerError.cannotAddSessionCaptureMetadataOutput}captureSession.addOutput(metadataOutput)metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)metadataOutput.metadataObjectTypes = [.qr]captureSessionConfigured = truereturn captureSession}

We forward the metadata object that was captured to the delegate for further processing. This has to be performed on the main thread and hence we specify the queue as DispatchQueue.main.

The metadataObjectTypes property tells the app what kind of metadata we are interested in. The .qr clearly indicates our purpose. We want to do QR code scanning.

func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {stopRunning()guard let readableCodeObject = metadataObjects.first  as? AVMetadataMachineReadableCodeObject  else {delegate?.qrScanning(result: .failure(.qrNotAvailable))return}@QRValidator var qrCode: String? = readableCodeObject.stringValueguard let qr = qrCode else {delegate?.qrScanning(result: .failure(.qrNotAvailable))return}AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))delegate?.qrScanning(result: .success(qr))}

We need to display the video captured by the device’s camera on screen. We do this by using an AVCaptureVideoPreviewLayer (added as a sublayer of the current view) in your ViewController, which actually is a CALayer.

private var scanner: QRScanner!
private func startCapturing(session: AVCaptureSession) {//Initialize video preview layer and add as subviewpreviewLayer = AVCaptureVideoPreviewLayer(session: session)previewLayer.frame = self.view.layer.bounds
previewLayer.videoGravity = .resizeAspectFillcameraView.layer.addSublayer(previewLayer)//start video capture_ = scanner.startRunning()
scannerView.layer.borderColor = UIColor.white.cgColorscannerView.layer.borderWidth = 3self.setupConstraints()
view.addSubview(torchLabel)view.addSubview(torchButton)view.bringSubviewToFront(torchButton)view.addSubview(scannerView)view.bringSubviewToFront(scannerView)view.addSubview(bottomView)view.bringSubviewToFront(bottomView)
setupMaskLayer()}

What does setupMaskLayer do? If you need the UI as shown below you can add a mask layer.

//setup mask layerlet maskLayer = CAShapeLayer()func setupMaskLayer() {self.maskLayer.removeFromSuperlayer()let path = CGMutablePath()path.addRect(cameraView.bounds)path.addRect(scannerView.frame)maskLayer.path = pathmaskLayer.fillColor = UIColor(red: 61.0 / 255.0, green: 56.0 / 255.0, blue: 56.0 / 255.0, alpha: 0.5).cgColormaskLayer.fillRule = .evenOddcameraView.layer.addSublayer(maskLayer)}

NOTE: Your CAShapeLayer might go out of bounds in your code, just add this bottom line accordingly.

// If you are doing this in view controller:
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupMaskLayer()
}

// If you are doing this in UIView using auto-layout
override func layoutSubviews() {
super.layoutSubviews()
setupMaskLayer()
}

// If you are doing this in UIView using manual setting of frame
override var frame: CGRect {
didSet {
setupMaskLayer()
}

That's all! If you have questions or suggestions please write a comment! 😊

--

--

Responses (1)