How to Capture QR Code Data in a Specific Area AVCaptureVideoPreviewLayer Using Swift?


BarcodeBC > Articles > How to Capture QR Code Data in a Specific Area AVCaptureVideoPreviewLayer Using Swift?


Capture QR Code Data in a Specific Area AVCaptureVideoPreviewLayer Using Swift

To capture QR Code data in a specific area of AVCaptureVideoPreviewLayer using Swift, you will need to use the AVFoundation framework. Here's a basic outline of the steps you can follow.

1. Import the AVFoundation framework into your project.

2. Create an AVCaptureSession object, which manages the flow of data from the camera to your app.

3. Create an AVCaptureDevice object to represent the device's camera.

4. Create an AVCaptureDeviceInput object, which connects the camera to the capture session.

5. Create an AVCaptureMetadataOutput object, which captures the metadata (including QR codes) from the camera feed.

6. Set the metadata object's metadataObjectTypes property to [AVMetadataObject.ObjectType.qr], to specify that you only want to capture QR codes.

7. Add the metadata object to the capture session using the addOutput(_:) method.

8. Create an AVCaptureVideoPreviewLayer object to display the camera feed in your app's interface.

9. Add the preview layer to your view hierarchy.

10. Use the metadataOutput(_ output: AVCaptureOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) method to process the metadata objects that are captured by the output object.

11. Use the bounds property of the preview layer to determine the specific area of the camera feed that you want to capture QR codes in.

12. Use the metadata object's bounds property to determine the location of the QR code in the camera feed.

13. If the QR code is located in the specific area you want, use the stringValue property of the metadata object to extract the data from the QR code.

Here's some example code that demonstrates these steps.


import AVFoundation
import UIKit

class ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
    var captureSession: AVCaptureSession!
    var previewLayer: AVCaptureVideoPreviewLayer!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let captureDevice = AVCaptureDevice.default(for: .video)
        
        do {
            let input = try AVCaptureDeviceInput(device: captureDevice!)
            
            captureSession = AVCaptureSession()
            captureSession.addInput(input)
            
            let metadataOutput = AVCaptureMetadataOutput()
            captureSession.addOutput(metadataOutput)
            
            metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            metadataOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]
            
            previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
            previewLayer.frame = view.bounds
            view.layer.addSublayer(previewLayer)
            
            captureSession.startRunning()
        } catch {
            print("Error setting up camera: \(error.localizedDescription)")
        }
    }
    
    func metadataOutput(_ output: AVCaptureOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        for metadataObject in metadataObjects {
            guard let qrCodeObject = metadataObject as? AVMetadataMachineReadableCodeObject else {
                continue
            }
            guard let previewLayer = previewLayer else {
                continue
            }
            let barCodeObject = previewLayer.transformedMetadataObject(for: qrCodeObject)
            if let bounds = barCodeObject?.bounds {
                if bounds.intersects(mySpecificArea) {
                    if let qrCodeString = qrCodeObject.stringValue {
                        print("QR code data found: \(qrCodeString)")
                    }
                }
            }
        }
    }
}

In this example, mySpecificArea is a CGRect that defines the specific area of the camera feed that you want to capture QR codes in. The metadataOutput(_:didOutput:from:) method processes the metadata objects that are captured by the output object, and extracts the data from any QR codes that are located in the specific area.


I'm doing a QR Code scan in the cornerFrame area but the scan doesn't return any results. I currently have cornerRect with corners green as an example of where I will want the user to scan the QR codes. How to achieve this?

If your QR code scan is not returning any results in the corner frame area, it's possible that the AVCaptureMetadataOutput is not capturing the QR code in that specific area. Here are a few things you can try to fix this issue:

1. Check the size and position of your cornerRect to make sure it covers the area where you want to capture QR codes. You can use the AVCaptureVideoPreviewLayer's bounds property to get the size and position of the camera preview layer, and then adjust the size and position of your cornerRect accordingly.

2. Check the orientation of the camera preview layer and your cornerRect. The camera preview layer may have a different orientation than your cornerRect, which can affect the location of the QR code in the output metadata. You can use the transformedMetadataObject(for:) method of AVCaptureVideoPreviewLayer to transform the metadata object to match the orientation of the camera preview layer.

3. Check the distance between the camera and the QR code. If the QR code is too close or too far from the camera, it may not be captured properly. You can try adjusting the focus of the camera or moving the device closer or further away from the QR code barcode.

Here's an example code snippet that shows how to adjust the position and size of cornerRect based on the position and size of the camera preview layer, and how to transform the metadata object to match the orientation of the camera preview layer:


import AVFoundation
import UIKit

class ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
    var captureSession: AVCaptureSession!
    var previewLayer: AVCaptureVideoPreviewLayer!
    var cornerRect: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let captureDevice = AVCaptureDevice.default(for: .video)
        
        do {
            let input = try AVCaptureDeviceInput(device: captureDevice!)
            
            captureSession = AVCaptureSession()
            captureSession.addInput(input)
            
            let metadataOutput = AVCaptureMetadataOutput()
            captureSession.addOutput(metadataOutput)
            
            metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            metadataOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]
            
            previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
            previewLayer.frame = view.bounds
            view.layer.addSublayer(previewLayer)
            
            cornerRect = UIView()
            cornerRect.backgroundColor = UIColor.green
            cornerRect.layer.borderWidth = 2
            cornerRect.layer.borderColor = UIColor.green.cgColor
            view.addSubview(cornerRect)
            
            captureSession.startRunning()
        } catch {
            print("Error setting up camera: \(error.localizedDescription)")
        }
    }
    
    func metadataOutput(_ output: AVCaptureOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        for metadataObject in metadataObjects {
            guard let qrCodeObject = metadataObject as? AVMetadataMachineReadableCodeObject else {
                continue
            }
            guard let previewLayer = previewLayer else {
                continue
            }
            let transformedObject = previewLayer.transformedMetadataObject(for: qrCodeObject)
            let cornerRectFrame = previewLayer.frame.intersection(cornerRect.frame)
            if transformedObject?.bounds.intersects(cornerRectFrame) ?? false {
                if let qrCodeString = qrCodeObject.stringValue {
                    print("QR code data found: \(qrCodeString)")
                }
            }
        }
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        let previewLayerFrame = view.bounds
        let cornerRectWidth = previewLayerFrame.width / 2
        let cornerRectHeight = previewLayerFrame.height