How to Build an iOS QR Code and Barcode Scanner with SwiftUI on Apple Silicon
SwiftUI is a robust UI framework designed for constructing native applications across all Apple platforms. This article will guide you through developing an iOS QR code scanner utilizing SwiftUI and the Dynamsoft Barcode Reader on an Apple Silicon Mac (M1/M2/M3/M4). The v11 barcode SDK is distributed as a single bundle (barcode-reader-spm) that you add to the project with Swift Package Manager — no CocoaPods, no Podfile, and no separate frameworks to link. One import DynamsoftCaptureVisionBundle gives you everything: the CaptureVisionRouter, the CameraEnhancer/CameraView camera stack, and the LicenseManager.
What you’ll build: A native iOS app that opens the camera, detects barcodes and QR codes in real time, and draws a highlighted quad plus the decoded text right on the preview as a per-barcode annotation — all built with SwiftUI and Dynamsoft Capture Vision.
Key Takeaways
- SwiftUI apps can integrate a full-featured barcode and QR code scanner by wrapping Dynamsoft’s
CameraViewin aUIViewRepresentablestruct. - Add the Dynamsoft Barcode Reader Bundle v11 to the project with Swift Package Manager (
https://github.com/Dynamsoft/barcode-reader-spm, productDynamsoftBarcodeReader). The Capture Vision Bundle (router, camera enhancer, license) is resolved automatically as a package dependency. - A single
import DynamsoftCaptureVisionBundlereplaces the five module imports that older v10 samples needed (DynamsoftCore,DynamsoftLicense,DynamsoftBarcodeReader,DynamsoftCaptureVisionRouter,DynamsoftCameraEnhancer). - The
CapturedResultReceiverprotocol delivers decoded results asynchronously. Each decoded barcode is drawn as a quad on the SDK’s barcode drawing layer, and its text is drawn on a custom annotation layer that follows the barcode. - License verification failures are surfaced to the UI as a SwiftUI alert instead of failing silently.
- This approach works on all Apple Silicon Macs (M1 through M4) and deploys to any iPhone or iPad running iOS 15.2 or later (the SDK itself supports iOS 13+).
Common Developer Questions
How do I scan QR codes from the camera in a SwiftUI iOS app?
Initialize Dynamsoft Camera Enhancer for the camera session, connect it to a CaptureVisionRouter, and implement CapturedResultReceiver so decoded barcodes are drawn on the camera view in real time. That is the core scanning loop used in this app.
How do I add the Dynamsoft barcode SDK to an iOS project?
Open Xcode, go to File > Add Package Dependencies…, paste https://github.com/Dynamsoft/barcode-reader-spm, and add the DynamsoftBarcodeReader product to your app target. Xcode resolves the package and downloads the SDK frameworks automatically on the first build. No Podfile or .xcworkspace is needed — open the .xcodeproj directly.
How do I wrap a UIKit camera view in SwiftUI with UIViewRepresentable?
Expose the SDK’s CameraView from a manager object, then host it inside SwiftUI through a UIViewRepresentable wrapper. The wrapper pins the CameraView to a container with Auto Layout constraints, so the overlay coordinates stay in sync with the preview across device sizes and rotations without manual frame management.
This article is Part 1 in a 6-Part Series.
- Part 1 - How to Build an iOS QR Code and Barcode Scanner with SwiftUI on Apple Silicon
- Part 2 - Build an iOS Passport and ID MRZ Scanner with SwiftUI and Dynamsoft Capture Vision
- Part 3 - Build a macOS Barcode Scanner with SwiftUI and a C++ Barcode SDK
- Part 4 - Build a SwiftUI Barcode Scanner for iOS and macOS with Dynamsoft Capture Vision
- Part 5 - Build a Cross-Platform SwiftUI Document Scanner for macOS and iOS
- Part 6 - How to Build a macOS Framework Wrapping C++ in Objective-C++ for Swift Barcode Scanning
Demo Video: iOS SwiftUI Barcode and QR Code Scanner
Prerequisites
Before starting, ensure you have the following tools and resources:
-
Xcode: The integrated development environment (IDE) for macOS, required for iOS app development. Xcode 16 or later is recommended (the sample project uses the modern synchronized-folder project format).
-
A Trial License Key for the Dynamsoft iOS Barcode SDK. Get a 30-day free trial license to unlock the full capabilities of the SDK for development and testing. A new device with a network connection can also pick up a short automatic trial, but a real trial key is the reliable path for development.
Build an iOS QR Code Scanner with SwiftUI Step by Step
Dynamsoft Camera Enhancer provides a camera view that simplifies starting a camera session with just a few lines of Swift code. In the v11 SDK the barcode engine, the Capture Vision router, and the camera enhancer ship together, so the whole barcode reading pipeline — camera, decoding, and license — is driven from one import. The following sections will guide you through integrating it into a SwiftUI project step by step.
Step 1: Create the Project and Add the SDK with Swift Package Manager
- Create a new iOS SwiftUI app in Xcode and name it
qrscanner. -
In Xcode, go to File > Add Package Dependencies… and paste the package URL:
https://github.com/Dynamsoft/barcode-reader-spm -
Keep the default dependency rule Up to Next Major Version (from
11.6.2000at the time of writing), click Add Package, then add theDynamsoftBarcodeReaderproduct to theqrscannerapp target.The package depends on Dynamsoft Capture Vision (
DynamsoftCaptureVisionBundle), which Xcode resolves automatically. The first build downloads the SDK.xcframeworks from Dynamsoft’s CDN, so make sure you have a network connection.
If you clone the sample repository instead of building from scratch, the package is already wired up — just open the project:
open qrscanner.xcodeproj
Step 2: Configure Camera Access Permission
Navigate to TARGETS > Info tab in Xcode and add the key Privacy - Camera Usage Description:
Privacy - Camera Usage Description

Step 3: Set the Dynamsoft Barcode Reader License Key
SwiftUI does not use an AppDelegate.swift file by default for app lifecycle management. However, you can integrate UIKit AppDelegate functionality using the App protocol and the @UIApplicationDelegateAdaptor property wrapper.
-
Add an
AppDelegate.swiftfile to your project and configure the license key within theapplication(_:didFinishLaunchingWithOptions:)method:import SwiftUI import UIKit import DynamsoftCaptureVisionBundle // Shared state that surfaces license verification errors as a SwiftUI alert. class LicenseState: ObservableObject { static let shared = LicenseState() @Published var isErrorPresented = false @Published var errorMessage = "" } class AppDelegate: UIResponder, UIApplicationDelegate, LicenseVerificationListener { func onLicenseVerified(_ isSuccess: Bool, error: Error?) { if !isSuccess { let message = error?.localizedDescription ?? "Unknown error" print("\(message)") DispatchQueue.main.async { LicenseState.shared.errorMessage = "\(message)\n\nPlease check the license key in AppDelegate.swift." LicenseState.shared.isErrorPresented = true } } } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { LicenseManager.initLicense("LICENSE-KEY", verificationDelegate: self) return true } }Remember to replace
LICENSE-KEYwith your actual Dynamsoft Barcode Reader license key.LicenseStatepublishes the verification error so thatContentViewcan show an in-app alert (see Step 6) instead of failing silently when the key is missing or expired. -
In your
qrscannerApp.swiftfile, utilize the@UIApplicationDelegateAdaptorto designate yourAppDelegateclass as the delegate forUIApplication:import SwiftUI @main struct qrscannerApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { ContentView() } } }
Step 4: Build the Camera Manager
Create a CameraManager.swift file. The class owns the three SDK objects that make up the pipeline: the CameraView (a UIView subclass that renders the preview and the drawing layers), the CameraEnhancer (dce), and the CaptureVisionRouter (cvr), which runs the barcode-reading task on every camera frame:
import Foundation
import UIKit
import DynamsoftCaptureVisionBundle
class CameraManager: NSObject, ObservableObject, CapturedResultReceiver {
// Preset drawing layer for barcode results: 1 = DDN, 2 = DBR, 3 = DLR
private let barcodeDrawingLayerId: UInt = 2
private let labelHeight: UInt = 22
private let labelWidth: UInt = 240
private var labelDrawingLayerId: UInt = 0
private var cameraView = CameraView()
private let dce = CameraEnhancer()
private let cvr = CaptureVisionRouter()
override init() {
super.init()
setUpDCV()
}
The setup method enables the barcode drawing layer and creates a dedicated layer for the text annotations. The annotation layer gets a drawing style — white text on a translucent dark background — so the labels stay readable on any camera scene:
func setUpDCV() {
// Show the barcode drawing layer so decoded results are drawn on the camera view.
cameraView.getDrawingLayer(barcodeDrawingLayerId)?.visible = true
// A custom layer that holds one text annotation per decoded barcode.
let labelStyleId = DrawingStyleManager.createDrawingStyle(
UIColor.black.withAlphaComponent(0.55),
strokeWidth: 1,
fill: UIColor.black.withAlphaComponent(0.55),
textColor: .white,
font: .systemFont(ofSize: 12)
)
let labelLayer = cameraView.createDrawingLayer()
labelLayer.visible = true
labelLayer.setDefaultStyle(labelStyleId)
labelDrawingLayerId = labelLayer.layerId
dce.cameraView = cameraView
// Set the camera enhancer as the input.
try! cvr.setInput(dce)
// Add CapturedResultReceiver to receive the result callback when a video frame is processed.
cvr.addResultReceiver(self)
}
func getCameraView() -> CameraView {
return cameraView
}
When a frame contains decoded barcodes, the receiver callback clears both layers and redraws them: a quad around every barcode plus a text label anchored to it. The label is placed above the quad when there is room, and falls back to below its top edge when the barcode is close to the top of the screen:
func onDecodedBarcodesReceived(_ result: DecodedBarcodesResult) {
guard let items = result.items, items.count > 0,
let barcodeLayer = cameraView.getDrawingLayer(barcodeDrawingLayerId),
let labelLayer = cameraView.getDrawingLayer(labelDrawingLayerId) else {
return
}
barcodeLayer.clearDrawingItems()
labelLayer.clearDrawingItems()
for item in items {
barcodeLayer.addDrawingItems([QuadDrawingItem(quadrilateral: item.location)])
// Place the text annotation above the barcode quad.
guard let points = item.location.points as? [CGPoint], points.count == 4 else { continue }
let minX = points.map { $0.x }.min() ?? 0
let minY = points.map { $0.y }.min() ?? 0
let gap: CGFloat = 6
var y = minY - CGFloat(labelHeight) - gap
if y < 2 {
y = minY + gap
}
let labelItem = TextDrawingItem(
text: item.text,
topLeftPoint: CGPoint(x: minX, y: y),
width: labelWidth,
height: labelHeight
)
labelLayer.addDrawingItems([labelItem])
}
}
Finally, manage the view lifecycle to switch the camera session on and off:
func viewDidAppear() {
dce.open()
cvr.startCapturing(PresetTemplate.readBarcodes.rawValue) { isSuccess, error in
if (!isSuccess) {
if let error = error {
print(error.localizedDescription)
}
}
}
}
func viewDidDisappear() {
dce.close()
cvr.stopCapturing()
}
}
Step 5: Create a SwiftUI Camera View for QR Code Scanning
Since CameraView is a UIView subclass, it needs to be wrapped in a UIViewRepresentable struct for use in SwiftUI. The wrapper returns a container and pins the CameraView to it with Auto Layout. The camera preview then always fills the space SwiftUI gives it, and the overlay drawn by the SDK stays aligned with the preview even when the device rotates or the layout changes:
import Foundation
import SwiftUI
import DynamsoftCaptureVisionBundle
struct DynamsoftCameraView: UIViewRepresentable {
var cameraManager: CameraManager
func makeUIView(context: Context) -> UIView {
let container = UIView()
container.backgroundColor = .black
let cameraView = cameraManager.getCameraView()
cameraView.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(cameraView)
NSLayoutConstraint.activate([
cameraView.topAnchor.constraint(equalTo: container.topAnchor),
cameraView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
cameraView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
cameraView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
])
return container
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
Step 6: Construct the SwiftUI View Hierarchy
In the ContentView.swift file, create a ContentView struct containing a DynamsoftCameraView instance. The camera fills the whole screen, with a small title on top. The .alert modifier observes LicenseState and pops up an alert if license verification fails:
import SwiftUI
struct ContentView: View {
@ObservedObject private var cameraManager = CameraManager()
@ObservedObject private var licenseState = LicenseState.shared
var body: some View {
ZStack {
DynamsoftCameraView(cameraManager: cameraManager)
.ignoresSafeArea()
.onAppear() {
cameraManager.viewDidAppear()
}.onDisappear(){
cameraManager.viewDidDisappear()
}
VStack {
Text("iOS QR Code Scanner")
.font(.title)
.foregroundColor(.orange)
.padding(.top, 8)
Spacer()
}
}
.alert("License Error", isPresented: $licenseState.isErrorPresented) {
Button("OK", role: .cancel) {}
} message: {
Text(licenseState.errorMessage)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Step 7: Deploy and Test the Scanner on iPhone or iPad
Configure the signing settings for your Xcode project (select your development team on the Signing & Capabilities tab of the target), then deploy and test the app on a physical device. Grant the camera permission when the prompt appears. Scan a QR code or a barcode: you should see a quad drawn around it and the decoded text in the annotation right above it.

Common Issues and Edge Cases
- Camera permission denied at runtime: If the user taps “Don’t Allow” on the camera prompt, the preview stays black with no error. Check
AVCaptureDevice.authorizationStatus(for: .video)and show an alert directing the user to Settings > Privacy > Camera. - First build downloads the SDK: Xcode fetches the Dynamsoft
.xcframeworks from the network the first time it resolves the package. If the build fails with a download error, check your connection and try File > Packages > Reset Package Caches. - License errors are silent in the console but not on screen: If scanning returns nothing, look for the in-app “License Error” alert first — the sample raises it whenever
onLicenseVerifiedreports a failure (for example whenLICENSE-KEYwas not replaced). The error details are also printed to the Xcode console. - Overlay alignment on different screen sizes: The
CameraViewis pinned with Auto Layout inside theUIViewRepresentablecontainer (Step 5), so the SDK keeps the overlay coordinates in sync with the preview on any device size and rotation. Do not set the view frame fromUIScreen.main.boundsat init time.