Scan QR code in SwiftUI

If your app requires QR code scanning capabilities, or if you’re simply interested in this topic, you might find this post useful.

First, we’ll craft the primary view, integrating a toolbar button that unveils a sheet dedicated to scanning, with the QR code displayed centrally upon detection.

Subsequently, we’ll delineate the sheet featuring a closure mechanism, ensuring it automatically dismisses upon QR code identification.

Lastly, we’ll delve into the crux of the matter: the scanning process itself.

First thing we have to add to the permission the Privacy – Camera Usage Description otherwise the app will not start.

Introducing the ContentView:

struct ContentView: View {
    @State var qr = ""
    @State var scan = false
    
    var body: some View {
        NavigationStack {
            VStack {
                Text(qr)
            }.sheet(isPresented: $scan) {
                SheetView(qrCode: $qr)
            }
            .padding()
            .toolbar {
                Button(action: {
                    qr = ""
                    scan = true
                }) {
                    Image(systemName: "qrcode.viewfinder")
                }
            }
        }
    }
}

Thus, the SheetView is activated when the scan button is pressed, and any QR code scanned is stored in the QR variable.

Here’s a closer look at the SheetView:

import SwiftUI

struct SheetView: View {
    @Environment(\.dismiss) private var dismiss
    @Binding var qrCode: String
    var body: some View {
        VStack {
            QrScannerView(qr: $qrCode)
        }.overlay(
            VStack {
                Spacer()
                Button("Close") {
                    dismiss()
                }.foregroundStyle(.black)
                    .frame(maxWidth: 60, maxHeight: 60)
                    .background(.yellow)
                    .cornerRadius(10)
            }
        ).onChange(of: qrCode) {
            dismiss()
        }
    }
}

#Preview {
    @State var dum = ""
    return SheetView(qrCode: $dum)
}

This view incorporates the QrScannerView, which stands as the cornerstone of the application, alongside a button for closing the view. Upon detecting a QR code, the sheet is automatically dismissed via the onChange mechanism.

Now, onto the crucial part:

import SwiftUI
import VisionKit

@MainActor
struct QrScannerView: UIViewControllerRepresentable {
    @Binding var qr: String
   
    var scannerViewController: DataScannerViewController = DataScannerViewController(
        recognizedDataTypes: [.barcode()],
        qualityLevel: .accurate,
        recognizesMultipleItems: false,
        isHighFrameRateTrackingEnabled: false,
        isHighlightingEnabled: false
    )
    
    var scannerAvailable: Bool {
        DataScannerViewController.isSupported &&
        DataScannerViewController.isAvailable
    }
    
    
    func makeUIViewController(context: Context) -> DataScannerViewController {
        scannerViewController.delegate = context.coordinator
        return scannerViewController
    }
    
    func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) {
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
        
    class Coordinator: NSObject, DataScannerViewControllerDelegate {
        var parent: QrScannerView
        var qr = ""
        init(_ parent: QrScannerView) {
            self.parent = parent
            parent.start()

        }
        
        func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) {
            processAddedItems(items: addedItems)
        }
        
        func dataScanner(_ dataScanner: DataScannerViewController, didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) {
        }
        
        func dataScanner(_ dataScanner: DataScannerViewController, didUpdate updatedItems: [RecognizedItem], allItems: [RecognizedItem]) {
        }
        
        func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) {
            processItem(item: item)
        }
        
        
        func processAddedItems(items: [RecognizedItem]) {
            for item in items {
                processItem(item: item)
            }
        }

        func processItem(item: RecognizedItem) {
            switch item {
            case .barcode(let code):
                parent.qr = code.payloadStringValue ?? ""
            case .text(_):
                break
            @unknown default:
                print("Should not happen")
            }
        }
    }
}

To delve deeper, it’s essential to highlight a few key points, starting with the importation of VisionKit. This toolkit empowers developers to scan not only QR codes and barcodes but also text.

The view is annotated with @MainActor, indicating it executes on the main thread, ensuring the UI remains responsive and updated in real-time.

It implements the UIViewControllerRepresentable protocol, facilitating the integration of a UIViewController within SwiftUI. This approach enables SwiftUI views to manage traditional UIKit controllers seamlessly.

The qr variable is designated to store the string extracted from the QR code, serving as a bridge for data retrieved from scans.

scannerViewController encapsulates the scanner controller, initialized with specific properties tailored for barcode scanning, although it’s also capable of scanning text.

scannerAvailable assesses the availability of the scanner (and by extension, the camera), ensuring the device can perform the required operations.

updateUIViewController is responsible for configuring the controller post-initialization, while makeController initializes and returns the controller, setting the stage for its operation.

The controller adheres to the DataScannerControllerViewDelegate protocol, which is defined as an object that analyzes live video from the camera for text, data within text, and machine-readable codes, essentially handling the intricate aspects of scanning.

Scanning starts automatically upon the controller’s initialization, with a call to start from the parent object, streamlining the process for immediate action.

processItem meticulously analyzes discovered items; if an item is identified as a QR code, its string value is captured and processed accordingly.

The use of @unknown default in Swift provides a safeguard against potential incompatibilities that might arise in the future, ensuring the application remains robust across updates and changes in the development landscape.

The code https://github.com/niqt/swift-qr-code

Note: English is not my native language, so I apologize for any errors. I use AI solely to generate the banner of the post; the content is human-generated.

To subscribe to my newsletter [https://nicoladefilippo.com/#mailinglist]

share this post with friends

Picture of Nicola De filippo

Nicola De filippo

I'm a software engineer who adds to the passion for technologies the wisdom and the experience without losing the wonder for the world. I love to create new projects and to help people and teams to improve

Leave a comment

Your email address will not be published. Required fields are marked *

Who I am

I'm a software engineer who adds to the passion for technologies the wisdom and the experience without losing the wonder for the world. I love to create new projects and to help people and teams to improve.

Follow Me Here

Get The Latest Updates

Periodically receive my super contents on coding and programming

join the family;)

Recent Posts