civil-and-structural-engineering
Implementing Qr Code Scanning in Ios Using Avfoundation
Table of Contents
QR code scanning has become a staple feature in modern iOS applications, enabling quick data transfer, seamless authentication, and interactive marketing experiences. Apple's AVFoundation framework offers a robust, low-level API for real-time media capture and processing, including the detection of machine-readable codes like QR codes. This article provides a comprehensive guide to implementing QR code scanning in an iOS app using AVFoundation, covering setup, configuration, delegate handling, UI integration, best practices, and common troubleshooting techniques. By the end, you will have a production-ready scanner that functions reliably across devices and iOS versions.
Understanding AVFoundation for QR Code Detection
AVFoundation is the framework of choice for camera-based features on iOS. It abstracts hardware access and provides a pipeline for capturing video frames, processing metadata, and rendering previews. For QR code scanning, the key components are:
- AVCaptureSession – the central coordinator that manages data flow from input to output.
- AVCaptureDeviceInput – wraps the camera (usually the rear camera) as an input source.
- AVCaptureMetadataOutput – processes frames and extracts machine-readable metadata such as QR codes, barcodes, and faces.
- AVCaptureVideoPreviewLayer – displays the live camera feed on screen.
A capture session works by connecting one or more inputs to one or more outputs. For QR code scanning, you connect a camera input to a metadata output. The metadata output calls a delegate method each time a code is detected. The entire process runs on background threads, ensuring the UI remains responsive.
Setting Up the Project
Creating a New Xcode Project
Begin by creating a new iOS App project in Xcode. Choose Swift or Objective‑C – the API is identical. For this guide we use Swift.
Adding Camera Usage Permission
iOS requires explicit user permission to access the camera. Add the following key to your Info.plist file:
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to scan QR codes.</string>
Without this entry, AVCaptureDevice will throw an error and your app may crash. The description string is shown in the system permission dialog.
Checking Camera Availability
Before configuring a capture session, confirm that the device has a camera and that the app is authorized. Use AVCaptureDevice.authorizationStatus(for: .video) and request permission if needed. A best practice is to handle all possible authorization states (notDetermined, restricted, denied, authorized).
Configuring the Capture Session
The capture session is the heart of the scanning process. Below is the typical setup sequence. For clarity, we will present the code in a ViewController that conforms to AVCaptureMetadataOutputObjectsDelegate.
Creating and Configuring the Session
import AVFoundation
import UIKit
class QRScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
var captureSession: AVCaptureSession!
var previewLayer: AVCaptureVideoPreviewLayer!
override func viewDidLoad() {
super.viewDidLoad()
setupCaptureSession()
}
private func setupCaptureSession() {
captureSession = AVCaptureSession()
captureSession.sessionPreset = .high
// 1. Get the rear camera
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else {
print("Camera not available")
return
}
// 2. Create input
let videoInput: AVCaptureDeviceInput
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
print("Error creating camera input: \(error.localizedDescription)")
return
}
// 3. Add input to session
guard captureSession.canAddInput(videoInput) else {
print("Cannot add input")
return
}
captureSession.addInput(videoInput)
// 4. Create metadata output
let metadataOutput = AVCaptureMetadataOutput()
guard captureSession.canAddOutput(metadataOutput) else {
print("Cannot add metadata output")
return
}
captureSession.addOutput(metadataOutput)
// 5. Set delegate and queue
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
// 6. Specify QR code type (and optionally other types)
metadataOutput.metadataObjectTypes = [.qr]
// 7. Setup preview layer
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
// 8. Start session on a background thread
DispatchQueue.global(qos: .background).async {
self.captureSession.startRunning()
}
}
}
Note: Starting the session on a background thread prevents blocking the main queue during camera initialization. The session runs continuously until you stop it.
Selecting Metadata Object Types
In step 6 we set metadataOutput.metadataObjectTypes = [.qr]. AVFoundation supports many barcode symbologies (EAN‑13, PDF417, Aztec, etc.). If your app should scan multiple types, pass an array like [.qr, .ean13, .code128]. However, be aware that requesting more types may increase detection latency and power consumption.
Implementing the Delegate for QR Code Detection
The delegate method metadataOutput(_:didOutput:from:) is called when the metadata output matches one of the specified types. It passes an array of AVMetadataObject instances. For QR codes, each object is of type AVMetadataMachineReadableCodeObject. Extract the string value and handle it.
func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) {
// Stop scanning after first successful detection (optional)
captureSession.stopRunning()
// Process metadata objects
if let metadataObject = metadataObjects.first {
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
let stringValue = readableObject.stringValue else { return }
// Handle the scanned QR code
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
found(code: stringValue)
}
}
func found(code: String) {
// Present the result (e.g., show an alert, open a URL, or store data)
let alert = UIAlertController(title: "QR Code Scanned",
message: code,
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
// Optionally restart scanning
DispatchQueue.global(qos: .background).async {
self.captureSession.startRunning()
}
})
present(alert, animated: true)
}
This minimal implementation stops the capture session after the first detection, vibrates the device, and displays the result in an alert. If your app requires continuous scanning (e.g., scanning multiple codes without restarting), remove the stopRunning() call and manage the detection state manually.
Displaying the Camera Preview (AVCaptureVideoPreviewLayer)
The preview layer renders the live video feed from the camera to the screen. In the setup code above, we added it as a sublayer of the view’s main layer. To ensure proper layout, update the preview’s frame in viewDidLayoutSubviews():
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
previewLayer.frame = view.layer.bounds
}
Consider using a custom UIView subclass that hosts the preview layer for better encapsulation. You can also add a translucent overlay (e.g., a corner‑guide view) to indicate where the user should position the QR code.
Converting Coordinates for the Overlay
If you want to highlight the detected QR code with an animation rectangle, use previewLayer.transformedMetadataObject(for:) to convert the metadata object’s coordinates from capture‑device space to view‑layer coordinates.
if let transformedObject = previewLayer.transformedMetadataObject(for: metadataObject) as? AVMetadataMachineReadableCodeObject {
let bounds = transformedObject.bounds
// Draw a shape layer matching bounds
}
Handling QR Code Data and User Feedback
QR codes can encode URLs, plain text, contact information (vCard), Wi‑Fi credentials, and more. After extracting the string value, you should validate and react appropriately:
- URL detection – use
URL(string:)and if valid, open it withUIApplication.shared.open(url:). - Plain text – display in a label or copy to clipboard.
- Custom app schemes – parse your own format (e.g., “yourapp://action?param=value”).
Provide immediate haptic feedback via UIImpactFeedbackGenerator and optionally play a short sound. This confirms to the user that the scan succeeded.
Best Practices
Region of Interest
By default, AVFoundation scans the entire camera frame. To improve accuracy and reduce false positives, set the rectOfInterest property on AVCaptureMetadataOutput. The rectangle uses normalized coordinates (0‑1) relative to the camera image (not the preview layer). Coordinating this with the preview layer can be tricky; use metadataOutput.rectOfInterest = previewLayer.metadataOutputRectConverted(fromLayerRect: overlayView.frame).
Torch Control
In low‑light conditions, enable the camera torch. Check if the device has a torch (device.hasTorch) and set try device.lockForConfiguration() then device.torchMode = .on. Remember to unlock after configuration.
Performance Considerations
- Set
sessionPresetto.highor.medium. Higher presets consume more battery but yield better detection for small codes. - Use
DispatchQueue.global(qos: .userInitiated)for starting the session, and run the delegate queue on the main thread if you update the UI. For heavy processing, consider a custom serial queue. - Stop the session when the view disappears (
viewWillDisappear) to save resources.
Multiple Code Detection
To scan multiple codes in a single frame without stopping, iterate over metadataObjects and match each readable object. Manage a set of already‑scanned strings to avoid duplicate processing. Use a debounce timer to prevent rapid successive detections of the same code.
Troubleshooting Common Issues
Camera Permission Denied
If the user denies camera access, the capture session will fail to start. Present a dialog explaining why the permission is required and optionally provide a button to open the app’s settings via UIApplication.openSettingsURLString.
Capture Session Not Running
Check that canAddInput and canAddOutput return true. Also ensure the session starts on a background queue. A common mistake is to call startRunning() on the main thread, which can hang the UI.
Metadata Object Types Not Supported
Some old devices or iOS versions may not support certain barcode types. Always verify metadataOutput.availableMetadataObjectTypes before setting the metadataObjectTypes array. Fall back gracefully by showing an error message.
Preview Layer Not Visible
Make sure the preview layer is inserted at the correct index in the view’s layer hierarchy. If you use Auto Layout, update the layer frame in viewDidLayoutSubviews.
Advanced Features: Scanning Multiple Codes, Continuous Scanning, and Custom UI
Continuous Scanning Without Stopping
To allow scanning one code after another without restarting the session, remove the stopRunning() call. Instead, use a flag to ignore repeated detection of the same code within a short time window. For example:
private var lastScannedCode: String?
private var scanTimer: Timer?
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
for metadata in metadataObjects {
guard let readable = metadata as? AVMetadataMachineReadableCodeObject,
let value = readable.stringValue else { continue }
if value != lastScannedCode {
lastScannedCode = value
// handle code
// reset timer
scanTimer?.invalidate()
scanTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in
self.lastScannedCode = nil
}
}
}
}
Custom UI Overlays
Create a “scan area” view with transparent center and border guides. Use Core Graphics to draw a rectangle or cross‑hairs. Constrain the overlay to match the rectOfInterest region. Load a design that mimics popular scanner apps (e.g., a white corner bracket).
Handling Different QR Code Data Types
If your app needs to handle structured data like vCards or MeCards, parse the string accordingly. For example, a vCard string starts with “BEGIN:VCARD”. Use CNContactVCardSerialization to create a contact object. For Wi‑Fi credentials, parse the “WIFI:” prefix to extract SSID and password.
External Resources
For further reading, consult the official Apple documentation:
- Setting Up a Capture Session – Apple Developer Documentation
- AVMetadataMachineReadableCodeObject – Apple Developer Documentation
- AVCaptureMetadataOutput – Apple Developer Documentation
Additionally, the AVCamBarcode sample code provides a complete reference implementation.
Conclusion
Integrating QR code scanning into an iOS app with AVFoundation is a straightforward process when you understand the capture session pipeline. By following the steps outlined in this article – setting up permissions, configuring the session, implementing the metadata output delegate, and displaying the camera preview – you can add reliable code scanning to your app. Remember to handle edge cases like permission denial, device compatibility, and performance optimization. With the advanced tips provided, you can extend the scanner to support continuous scanning, custom UIs, and multiple barcode formats, delivering a polished user experience. Always refer to Apple’s official documentation for the latest API changes and best practices.