iOS

How to Make a QR Code or Barcode Scanner in SwiftUI

How to Make a QR Code or Barcode Scanner in SwiftUI

Thanks to iOS 16 and Apple’s DataScanner API, building a QR Code or Barcode Scanner in SwiftUI is now a breeze! In this tutorial, we’ll guide you through the simple steps of using the DataScannerViewController, UIViewControllerRepresentable, and data passing between SwiftUI and UIKit. And the best part? You’ll achieve all this with just a minimal amount of code.

How to use a UIKit View Controller in SwiftUI

Our first step is to bring all the code that Apple has already written for us into SwiftUI. To keep things clean and simple, I recommend wrapping the UIKit APIs in their own structure. This practical approach will make the call site in SwiftUI a breeze to work with.

Now, let’s add a new Swift file to our Xcode project and name it “DataScannerRepresentable”. Don’t worry; we’re here to guide you through the code that needs to be pasted inside. We’ve got your back every step of the way.

How to Make a QR Code or Barcode Scanner in SwiftUI

In today’s digital age, the use of QR (Quick Response) codes and barcodes has become ubiquitous in various industries, from retail to healthcare. These two-dimensional codes offer a convenient way to store and retrieve information quickly using a smartphone or a dedicated scanner. With SwiftUI, Apple’s declarative framework for building user interfaces, creating a QR or barcode scanner becomes even more accessible and efficient. In this guide, we’ll walk through the steps to build your own QR or barcode scanner in SwiftUI, empowering you to integrate this functionality seamlessly into your iOS apps.

  1. Understanding QR and Barcode Scanning:

    Before diving into the implementation, it’s essential to understand the basics of QR and barcode scanning. QR codes are square-shaped symbols that encode data such as URLs, text, or contact information. On the other hand, barcodes consist of a series of parallel lines of varying widths that represent numbers or alphanumeric characters.

  2. Setting Up the Project:

    Begin by creating a new SwiftUI project in Xcode. Open Xcode, select “Create a new Xcode project,” choose the “App” template under iOS, and select SwiftUI as the user interface framework.

  3. Displaying Scanned Results:

    Upon detecting a QR code or barcode, handle the scanned result appropriately. You can display the scanned data on the screen, perform a specific action based on the scanned content, or store the information for later use.

  4. Testing the Scanner:

    Test the scanner thoroughly on various devices and under different lighting conditions to ensure robust performance. Consider edge cases and handle them gracefully to provide a seamless user experience.

  5. Customizing the Scanner Interface:

    Customize the scanner interface to match the look and feel of your app. You can adjust the size and position of UI elements, apply styling, and add animations to enhance the user experience.

SwiftUI QR or Barcode Scanner Example.

Let’s Start to create a swiftUI QR and Barcode Scanner example using with VisionKit. start your Xcode and create a new project if you have not existing project follow below steps.

Step:- 1 Create a new swift File with name DataScannerRepresentable.

In the DataScannerRepresentable file we scan the QR code and store the result on QR and bar code. se below code snip.

In the above code in the above code, we must import SwiftUI since we are employing”@Binding” “@Binding” properties. We import VisionKit because it offers access to the Data Scanner API.

They are variables that enable us to transfer information between other SwiftUI views as well as our UIViewRepresentable.

“Class Coordinator” or the “class Coordinator” section is where the data is received from our scanners, be it text, barcodes or a fail-safe default value.

MakeUIViewController is the place where we’re creating the UIKit view which will be used in our SwiftUI view later on. It’s also where we can discover all the awesome capabilities that a scanner for code Apple offers for free. For instance, recognizedDataTypes lets us look up a particular type of code such as an ean8, qr pdf417, ean8, and other. This will also give us the capability to search for text. You can also turn off or enable the pinch-to-zoom feature and have it highlight codes that are recognized within the image preview or include helpful information for the user right on the screen!

The updateUIViewController function tells the view controller when it should start scanning.

The function makeCoordinator is the interface that connects SwiftUI and UIKit which allows both to work in conjunction with one with each.

import Foundation
import SwiftUI
import VisionKit

struct DataScannerRepresentable: UIViewControllerRepresentable {

    @Binding var shouldStartScanning: Bool
    @Binding var scannedText: String
    var dataToScanFor: Set<DataScannerViewController.RecognizedDataType>

    class Coordinator: NSObject, DataScannerViewControllerDelegate {
       var parent: DataScannerRepresentable

       init(_ parent: DataScannerRepresentable) {
           self.parent = parent
       }

        func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) {
            switch item {
            case .text(let text):
                parent.scannedText = text.transcript
            case .barcode(let barcode):
                parent.scannedText = barcode.payloadStringValue ?? "Unable to decode the scanned code"
            default:
                print("unexpected item")
            }
        }
    }

    func makeUIViewController(context: Context) -> DataScannerViewController {
        let dataScannerVC = DataScannerViewController(
            recognizedDataTypes: dataToScanFor,
            qualityLevel: .accurate,
            recognizesMultipleItems: true,
            isHighFrameRateTrackingEnabled: true,
            isPinchToZoomEnabled: true,
            isGuidanceEnabled: true,
            isHighlightingEnabled: true
        )
        dataScannerVC.delegate = context.coordinator
       return dataScannerVC
    }

    func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) {
       if shouldStartScanning {
           try? uiViewController.startScanning()
       } else {
           uiViewController.stopScanning()
       }
    }

    func makeCoordinator() -> Coordinator {
       Coordinator(self)
    }
}

Step 2: Adding Camera Usage Description

Before we are able to use our DataScanner within the application it is necessary to add a camera’s usage description in the info.plist file.

If we intend to make the app available on the app store, our exact description of the reason our app is required to access the camera is vital. The description should tell the users why your app requires access to your camera or what you intend to use the information for. A good method of coming up with your description is by asking yourself “If I was a brand new user to this app, why would I give them access to my camera?”

If we’re just trying out the code, it’s nevertheless a good idea to have a proper description of the camera’s usage. If you choose to publish this in the App Store later on, it’s very easy to forget to update this single part of the text. Additionally, Apple could reject your application in the event that the description isn’t adequate enough.

Adding Camera Usage Description

Step 3 : Create QRCodeScannerView for QR Code and Bar Code Scanner SwiftUI

In our view, where we would like to show scanner, we will need to import VisionKit to determine whether the DataScanner is supported and available in our system.

If the scanner supports it and available We are employing ZStack (or depth stack) ZStack (or depth stack) to overlay the scanned text over the top of the scanner. By telling the scanner that we are looking for an specific barcode set “.barcode(symbologies: [.qr])”, the scanner will only search for qr codes. If we were to locate any barcodes (including the qr) it would be better to change the wording to “.barcode ()”.

import Foundation
import SwiftUI
import VisionKit

struct QRCodeScannerView: View {

    @State var isShowingScanner = true
    @State private var scannedText = ""

    var body: some View {
        if DataScannerViewController.isSupported && DataScannerViewController.isAvailable {
        GeometryReader { geometry in
            let cutoutWidth: CGFloat = min(geometry.size.width, geometry.size.height) / 1.5

            ZStack (alignment: .bottom){

                DataScannerRepresentable(
                    shouldStartScanning: $isShowingScanner,
                    scannedText: $scannedText,
                    dataToScanFor: [.barcode(symbologies: [.qr])]
                )

                Text(scannedText)
                    .padding()
                    .background(Color.white)
                    .foregroundColor(.black)
                    .padding(.bottom,90)
                
            }.compositingGroup()

            
            Path { path in
                let left = (geometry.size.width - cutoutWidth) / 2.0
                let right = left + cutoutWidth
                let top = (geometry.size.height - cutoutWidth) / 2.0
                let bottom = top + cutoutWidth

                path.addPath(
                    createCornersPath(
                        left: left, top: top,
                        right: right, bottom: bottom,
                        cornerRadius: 40, cornerLength: 20
                    )
                )
            }
            .stroke(Color.blue, lineWidth: 8)
            .frame(width: cutoutWidth, height: cutoutWidth, alignment: .center)
            .aspectRatio(1, contentMode: .fit)
            
        }.edgesIgnoringSafeArea(.top)
        .edgesIgnoringSafeArea(.bottom)

        } else if !DataScannerViewController.isSupported {
            Text("It looks like this device doesn't support the DataScannerViewController")
        } else {
            Text("It appears your camera may not be available")
        }
    }

    

    

    

    private func createCornersPath(

        left: CGFloat,
        top: CGFloat,
        right: CGFloat,
        bottom: CGFloat,
        cornerRadius: CGFloat,
        cornerLength: CGFloat
    ) -> Path {

        var path = Path()
        // top left
        path.move(to: CGPoint(x: left, y: (top + cornerRadius / 2.0)))
        path.addArc(
            center: CGPoint(x: (left + cornerRadius / 2.0), y: (top + cornerRadius / 2.0)),
            radius: cornerRadius / 2.0,
            startAngle: Angle(degrees: 180.0),
            endAngle: Angle(degrees: 270.0),
            clockwise: false
        )

        path.move(to: CGPoint(x: left + (cornerRadius / 2.0), y: top))
        path.addLine(to: CGPoint(x: left + (cornerRadius / 2.0) + cornerLength, y: top))
        path.move(to: CGPoint(x: left, y: top + (cornerRadius / 2.0)))
        path.addLine(to: CGPoint(x: left, y: top + (cornerRadius / 2.0) + cornerLength))
        // top right
        path.move(to: CGPoint(x: right - cornerRadius / 2.0, y: top))
        path.addArc(
            center: CGPoint(x: (right - cornerRadius / 2.0), y: (top + cornerRadius / 2.0)),
            radius: cornerRadius / 2.0,
            startAngle: Angle(degrees: 270.0),
            endAngle: Angle(degrees: 360.0),
            clockwise: false
        )
        path.move(to: CGPoint(x: right - (cornerRadius / 2.0), y: top))
        path.addLine(to: CGPoint(x: right - (cornerRadius / 2.0) - cornerLength, y: top))
        path.move(to: CGPoint(x: right, y: top + (cornerRadius / 2.0)))
        path.addLine(to: CGPoint(x: right, y: top + (cornerRadius / 2.0) + cornerLength))
        // bottom left
        path.move(to: CGPoint(x: left + cornerRadius / 2.0, y: bottom))
        path.addArc(
            center: CGPoint(x: (left + cornerRadius / 2.0), y: (bottom - cornerRadius / 2.0)),
            radius: cornerRadius / 2.0,
            startAngle: Angle(degrees: 90.0),
            endAngle: Angle(degrees: 180.0),
            clockwise: false
        )

       path.move(to: CGPoint(x: left + (cornerRadius / 2.0), y: bottom))
        path.addLine(to: CGPoint(x: left + (cornerRadius / 2.0) + cornerLength, y: bottom))
        path.move(to: CGPoint(x: left, y: bottom - (cornerRadius / 2.0)))
        path.addLine(to: CGPoint(x: left, y: bottom - (cornerRadius / 2.0) - cornerLength))
        // bottom right
        path.move(to: CGPoint(x: right, y: bottom - cornerRadius / 2.0))
        path.addArc(
            center: CGPoint(x: (right - cornerRadius / 2.0), y: (bottom - cornerRadius / 2.0)),
            radius: cornerRadius / 2.0,
            startAngle: Angle(degrees: 0.0),
            endAngle: Angle(degrees: 90.0),
            clockwise: false
        )

        path.move(to: CGPoint(x: right - (cornerRadius / 2.0), y: bottom))
        path.addLine(to: CGPoint(x: right - (cornerRadius / 2.0) - cornerLength, y: bottom))
        path.move(to: CGPoint(x: right, y: bottom - (cornerRadius / 2.0)))
        path.addLine(to: CGPoint(x: right, y: bottom - (cornerRadius / 2.0) - cornerLength))
        return path
    }
}

struct QRView_Previews: PreviewProvider {
    static var previews: some View {
        QRCodeScannerView()
    }
}

Let’s run your project and check output.

 

Read More Tutorial