civil-and-structural-engineering
Developing a Barcode and Qr Code Scanner in Ios with Avfoundation
Table of Contents
Understanding AVFoundation for Barcode and QR Code Scanning
Apple’s AVFoundation framework provides a full-featured, low-level interface for working with audiovisual media on iOS devices. For barcode and QR code scanning, the framework exposes the AVCaptureMetadataOutput class, which can detect a wide range of machine-readable codes in real time from the camera feed. This includes QR codes, EAN-8/13, Code 128, PDF417, Aztec, and many other formats. The framework handles the heavy lifting of image analysis and code decoding, allowing developers to focus on application logic rather than implementing custom scanning algorithms.
AVFoundation scanning is hardware‑accelerated on modern devices, offering fast and reliable detection even in challenging lighting conditions. It works with both front and rear cameras, and the detection area can be constrained to a specific region of interest to improve performance and user experience.
Setting Up the Camera Capture Session
The core of any AVFoundation‑based scanner is the AVCaptureSession. This object orchestrates the data flow from the camera input to the metadata output. Setting up the session requires several boilerplate steps, which are best implemented in a dedicated view controller or manager class.
Requesting Camera Permission
Before accessing the camera, you must ask for user permission. Starting in iOS 10, you need to include the NSCameraUsageDescription key in your Info.plist file with a string explaining why your app needs the camera. Permission is requested using AVCaptureDevice.requestAccess(for: .video). Handle both cases – granted and denied – and update your UI accordingly.
AVCaptureDevice.requestAccess(for: .video) { granted in
if granted {
// Configure and start the capture session
} else {
// Show an alert guiding the user to Settings
}
}
Creating the AVCaptureSession
Once permission is granted, instantiate an AVCaptureSession. Configure its session preset to control the video quality; for barcode scanning, .high is a good default, but you can also use .medium for faster processing.
let session = AVCaptureSession()
session.sessionPreset = .high
Adding the Camera Input
Obtain the default video device (the rear camera) and create an AVCaptureDeviceInput. If the input cannot be added to the session (e.g., the device is busy), catch the error and present feedback to the user.
guard let camera = AVCaptureDevice.default(for: .video) else { return }
do {
let input = try AVCaptureDeviceInput(device: camera)
guard session.canAddInput(input) else { return }
session.addInput(input)
} catch {
print("Camera input error: \(error.localizedDescription)")
}
Adding the Metadata Output
Create an AVCaptureMetadataOutput instance, set its delegate (on a dedicated serial queue), and add it to the session. The delegate will receive detected metadata objects.
let metadataOutput = AVCaptureMetadataOutput()
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
guard session.canAddOutput(metadataOutput) else { return }
session.addOutput(metadataOutput)
// Specify which code types to detect – must be done after the output is added
metadataOutput.metadataObjectTypes = [.qr, .ean13, .code128, .pdf417]
Important: metadataObjectTypes must be set after the output is added to the session. Setting it earlier will cause a crash.
Starting the Session
Run the session on a background thread to keep the UI responsive. Call session.startRunning() in a background dispatch queue or on a dedicated serial queue.
DispatchQueue.global(qos: .userInitiated).async {
session.startRunning()
}
Implementing the Delegate Method
The delegate method metadataOutput(_:didOutput:from:) is called whenever the scanner detects one or more codes. This method receives an array of AVMetadataObject instances, which you need to cast to AVMetadataMachineReadableCodeObject to access the decoded string.
func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) {
// Process only the first detected code (most common pattern)
guard let metadataObject = metadataObjects.first,
let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
let stringValue = readableObject.stringValue else { return }
// Use the scanned value (e.g., display, validate, or send to server)
handleScannedCode(stringValue, type: readableObject.type)
}
It’s common to stop the session after a successful scan to prevent repeated detections. Call session.stopRunning() after handling the code, and restart it when the user resets the scanner.
Handling Detected Codes
Once you have the string value, determine what action to take based on the code type and content. Common scenarios include:
- URLs – Open in Safari or an in‑app browser using
UIApplication.shared.open(url). - Wi‑Fi credentials – Parse the standard format (
WIFI:T:WPA;S:network;P:password;;) and connect programmatically (requires additional entitlement). - Product codes – Look up product info via an API such as the UPC Database or your own inventory system.
- Contact information – Parse vCard or MeCard formats and create a new contact in the address book.
- Plain text – Display in an alert or copy to pasteboard.
For each code type, validate the string before processing. Malformed or malicious codes should be ignored or reported gracefully.
Best Practices for Scanning Performance
To ensure smooth and efficient scanning, follow these guidelines:
- Limit metadata object types – Only enable the code formats your app actually needs. Every extra format adds processing overhead.
- Set a region of interest – Use
metadataOutput.rectOfInterestto define a small portion of the video feed where codes are expected. This dramatically improves detection speed and reduces false positives. - Use the correct video gravity –
.resizeAspectFillkeeps the camera preview proportional and is ideal for overlaying scan‑area guides. - Leverage a serial queue for delegate callbacks – Perform heavy processing (e.g., network lookups) off the main thread to keep the UI responsive.
- Consider torch support – Allow the user to toggle the camera’s torch for low‑light scenarios. Check
hasTorchbefore toggling. - Reset scanning after handling – After a successful scan, restart the session with a short delay (e.g., 1–2 seconds) to prevent immediate re‑detection of the same code.
Providing User Feedback
A good scanning experience includes visual and auditory cues. Implement these feedback mechanisms to guide the user:
- Visual overlay – Draw a highlight rectangle around the detected code using the
cornersproperty ofAVMetadataMachineReadableCodeObject. This requires converting metadata coordinates to view coordinates viapreviewLayer.transformedMetadataObject(for:). - Animation – Play a subtle animation (e.g., a shrinking border) when a code is recognized.
- Sound – Play a system sound using
AudioServicesPlaySystemSoundwith a short, positive tone like1103(the “Mail Sent” sound). - Haptic feedback – Use
UIImpactFeedbackGeneratororUINotificationFeedbackGeneratorfor a subtle vibration. - Scan area guide – Display a translucent rectangle with corner guides to help users center the code.
Error Handling and Edge Cases
Robust error handling ensures your scanner degrades gracefully when things go wrong.
- Camera unavailable – Check
AVCaptureDevice.isAuthorized(for: .video)andisAvailableon the device. On iPod touch or simulator without a camera, present a meaningful message. - Permission denied – Show an alert with a button that opens Settings via
UIApplication.openSettingsURLString. - Session configuration failure – Log errors when
canAddInputorcanAddOutputreturnsfalse. This can happen if another app is using the camera in the background (rare). - Low light or blurry images – Reduce the detection region of interest and ensure the camera autofocus is enabled. Prompt the user to increase light or steady the device.
- Invalid or unsupported code types – The
metadataObjectTypesproperty may not support all formats on all devices. UseAVCaptureMetadataOutput.availableMetadataObjectTypesto query compatibility before setting.
Testing Across Devices
AVFoundation barcode scanning performance varies significantly across generations of iPhone and iPad. Test your implementation on:
- Older devices (iPhone 6s, iPad Air 2) – These have slower processors and may struggle with high‑resolution presets. Use
.hd1280x720for optimal balance on older hardware. - Devices with different camera optics – iPhones with telephoto or ultra‑wide cameras may focus differently. Always use the default wide‑angle camera for barcode scanning.
- Low‑light environments – Test in dim conditions to ensure the scan overlay remains visible and the torch toggle works.
- Various code sizes and orientations – Try codes from 1 cm × 1 cm stickers to large posters, and test landscape vs. portrait scanning.
Advanced Considerations
For production apps, consider adding features like continuous scanning (allowing multiple codes in one session), zoom controls, or integration with ARKit for augmented reality overlays. If you need to process codes from still images (e.g., photo library), use CIDetector or VNDetectBarcodesRequest from the Vision framework, though AVFoundation is generally faster for real‑time video.
For a deeper dive into AVFoundation configuration, refer to Apple’s Setting Up a Capture Session guide and the AVCaptureMetadataOutput documentation. You can also find a complete sample project in the Scanning Barcodes with AVCaptureMetadataOutput article.
By following these guidelines and thoroughly testing your implementation, you can deliver a polished, high‑performance barcode and QR code scanner that enhances your iOS app’s usability and reliability.