Scanbot SDK has been acquired by Apryse! Learn more

Learn more
Skip to content

Using Google ML Kit on iOS to build a barcode scanner app with Swift

Ivan November 13, 2025 13 mins read
iOS ML Kit barcode scanner tutorial

In this tutorial, you’ll learn how to use Google’s ML Kit library to build a barcode scanning app for iOS with Xcode and SwiftUI. We’ll use CocoaPods to integrate the GoogleMLKit/BarcodeScanning library into our project.

Scanning a barcode with Google ML Kit on iOS

To build our app, we’ll follow these steps:

  1. Configuring the Xcode project
  2. Installing the ML Kit dependencies via CocoaPods
  3. Implementing the barcode scanning functionalities
  4. Embedding the barcode scanner view
  5. Configuring camera permissions

Prerequisites

  • A Mac with an up-to-date version of Xcode
  • CocoaPods installed on your Mac
  • A physical iOS device, since the iOS simulator lacks camera access

Step 1: Configure the Xcode project

Open Xcode and create a new iOS app project with SwiftUI as the interface. In this tutorial, we’ll go with “iOS ML Kit Barcode Scanner” as the product name.

When you’ve finished setting up your project, the next step is to disable script sandboxing. Xcode’s sandbox prevents scripts from writing to files for security reasons. However, CocoaPods relies on these scripts to manage dependencies and generate necessary project files.

To do this, select your project in Xcode’s project navigator, select it once more in the project and targets list, and open the Build Settings tab. Then use the search bar on the top right to filter for “User Script Sandboxing“. Toggle the setting to “No“. Repeat this for your target if necessary.

Disabling script sandboxing in Xcode

Step 2: Install the ML Kit dependencies via CocoaPods

Next, close Xcode (since CocoaPods won’t work properly if Xcode is open) and open a terminal. Navigate to your project directory (the one containing the .xcodeproj file), then run the following command to create a Podfile for your project:

pod init

Open the Podfile in your preferred text editor …

open -a TextEdit Podfile

… and replace its content with the following:

platform :ios, '15.0'
use_frameworks!

target 'iOS ML Kit Barcode Scanner' do
  # Pods for ML Kit
  pod 'GoogleMLKit/BarcodeScanning', '4.0.0'

  post_install do |installer|
      installer.generated_projects.each do |project|
            project.targets.each do |target|
                target.build_configurations.each do |config|
                    config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
                 end
            end
     end
  end
end

💡 The post_install hook automatically updates all pod dependencies to use iOS 15.0 as their minimum deployment target, preventing Xcode warnings about version mismatches between your app and its dependencies.

Save the Podfile, and back in your terminal, run the following command to install the GoogleMLKit/BarcodeScanning dependency:

pod install

Step 3: Implement the barcode scanning functionalities

In your project folder, you should now see an .xcworkspace file. Open this file to get back into your project. This ensures Xcode recognizes the CocoaPods dependencies.

In the directory that contains your project’s Swift files (one level down from the project’s root folder), create a new BarcodeScannerView.swift file and paste the following code:

import SwiftUI
import AVFoundation
import Combine 
import MLKitBarcodeScanning
import MLKitVision

struct BarcodeScannerView: View {
    @StateObject private var scanner = BarcodeScanner()
    @State private var scannedCode: String = ""
    @State private var showAlert = false

    var body: some View {
        ZStack {
            CameraView(scanner: scanner)
                .edgesIgnoringSafeArea(.all)

            VStack {
                Text("Scan a Barcode")
                    .font(.title)
                    .fontWeight(.bold)
                    .foregroundColor(.white)
                    .padding()
                    .background(Color.black.opacity(0.7))
                    .cornerRadius(10)
                    .padding(.top, 50)

                Spacer()

                if !scannedCode.isEmpty {
                    VStack(spacing: 10) {
                        Text("Scanned Code:")
                            .font(.headline)
                            .foregroundColor(.white)

                        Text(scannedCode)
                            .font(.system(size: 16, weight: .medium, design: .monospaced))
                            .foregroundColor(.white)
                            .padding()
                            .background(Color.green.opacity(0.8))
                            .cornerRadius(10)
                            .padding(.horizontal)
                    }
                    .padding(.bottom, 50)
                }
            }
        }
        .onReceive(scanner.$detectedBarcode) { barcode in
            if let barcode = barcode {
                scannedCode = barcode
                showAlert = true
            }
        }
        .alert("Barcode Detected", isPresented: $showAlert) {
            Button("OK") {
                scanner.resetDetection()
            }
        } message: {
            Text("Value: \(scannedCode)")
        }
    }
}

class BarcodeScanner: NSObject, ObservableObject {
    @Published var detectedBarcode: String?
    private var captureSession: AVCaptureSession?
    private let barcodeScanner = MLKitBarcodeScanning.BarcodeScanner.barcodeScanner()

    func setupCamera(session: AVCaptureSession) {
        self.captureSession = session
    }

    func scanBarcode(from sampleBuffer: CMSampleBuffer) {
        guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }

        let visionImage = VisionImage(buffer: sampleBuffer)
        visionImage.orientation = .up

        barcodeScanner.process(visionImage) { [weak self] barcodes, error in
            guard let self = self, error == nil, let barcodes = barcodes, !barcodes.isEmpty else {
                return
            }

            if let firstBarcode = barcodes.first, let rawValue = firstBarcode.rawValue {
                DispatchQueue.main.async {
                    if self.detectedBarcode == nil {
                        self.detectedBarcode = rawValue
                    }
                }
            }
        }
    }

    func resetDetection() {
        detectedBarcode = nil
    }
}

struct CameraView: UIViewRepresentable {
    @ObservedObject var scanner: BarcodeScanner

    func makeUIView(context: Context) -> UIView {
        let view = UIView(frame: .zero)

        let captureSession = AVCaptureSession()
        captureSession.sessionPreset = .high

        guard let videoCaptureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
              let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice),
              captureSession.canAddInput(videoInput) else {
            return view
        }

        captureSession.addInput(videoInput)

        let videoOutput = AVCaptureVideoDataOutput()
        videoOutput.setSampleBufferDelegate(context.coordinator, queue: DispatchQueue(label: "videoQueue"))

        if captureSession.canAddOutput(videoOutput) {
            captureSession.addOutput(videoOutput)
        }

        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.frame = view.bounds
        previewLayer.videoGravity = .resizeAspectFill
        view.layer.addSublayer(previewLayer)

        context.coordinator.previewLayer = previewLayer

        scanner.setupCamera(session: captureSession)

        DispatchQueue.global(qos: .userInitiated).async {
            captureSession.startRunning()
        }

        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        if let previewLayer = context.coordinator.previewLayer {
            DispatchQueue.main.async {
                previewLayer.frame = uiView.bounds
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(scanner: scanner)
    }

    class Coordinator: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
        var scanner: BarcodeScanner
        var previewLayer: AVCaptureVideoPreviewLayer?

        init(scanner: BarcodeScanner) {
            self.scanner = scanner
        }

        func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
            scanner.scanBarcode(from: sampleBuffer)
        }
    }
}

// MARK: - Preview
struct BarcodeScannerView_Previews: PreviewProvider {
    static var previews: some View {
        BarcodeScannerView()
    }
}

Let’s break down the most important parts.

Entry point: showing the scanner

struct ContentView: View {
    var body: some View {
        BarcodeScannerView()
    }
}

ContentView is the app’s main screen. It simply loads BarcodeScannerView, which handles everything – camera setup, barcode scanning, and displaying results.

Barcode scanner view: Camera + UI + detection

struct BarcodeScannerView: View {
    @StateObject private var scanner = BarcodeScanner()
    @State private var scannedCode: String = ""
    @State private var showAlert = false

Here we create:

  • scanner: handles ML Kit barcode scanning
  • scannedCode: stores the detected barcode’s value
  • showAlert: controls the popup when a barcode is found

Camera and overlay UI

ZStack {
    CameraView(scanner: scanner)
        .edgesIgnoringSafeArea(.all)

    VStack {
        Text("Scan a Barcode")
            .font(.title)
            .foregroundColor(.white)
            .padding()
            .background(Color.black.opacity(0.7))
            .cornerRadius(10)
            .padding(.top, 50)

        Spacer()

The ZStack places the camera preview behind the UI text. The title tells the user to scan a barcode.

Displaying the scanned result

if !scannedCode.isEmpty {
    VStack(spacing: 10) {
        Text("Scanned Code:")
        Text(scannedCode)
            .font(.system(size: 16, weight: .medium, design: .monospaced))
            .foregroundColor(.white)
            .padding()
            .background(Color.green.opacity(0.8))
            .cornerRadius(10)
    }
    .padding(.bottom, 50)
}

Once a barcode is detected, its value appears neatly at the bottom of the screen.

Handling detection and alert

.onReceive(scanner.$detectedBarcode) { barcode in
    if let barcode = barcode {
        scannedCode = barcode
        showAlert = true
    }
}
.alert("Barcode Detected", isPresented: $showAlert) {
    Button("OK") {
        scanner.resetDetection()
    }
} message: {
    Text("Value: \(scannedCode)")
}

SwiftUI listens to scanner.$detectedBarcode. When ML Kit finds a barcode, it updates scannedCode, shows an alert, and resets when confirmed.

Barcode detection logic (ML Kit + AVFoundation)

class BarcodeScanner: NSObject, ObservableObject {
    @Published var detectedBarcode: String?
    private var captureSession: AVCaptureSession?
    private let barcodeScanner = MLKitBarcodeScanning.BarcodeScanner.barcodeScanner()

This class connects camera frames to ML Kit. @Published allows SwiftUI to react when a barcode is detected.

Scanning each frame

func scanBarcode(from sampleBuffer: CMSampleBuffer) {
    guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }

    let visionImage = VisionImage(buffer: sampleBuffer)
    visionImage.orientation = .up

    barcodeScanner.process(visionImage) { [weak self] barcodes, error in
        guard let self = self, error == nil, let barcodes = barcodes, !barcodes.isEmpty else { return }

        if let rawValue = barcodes.first?.rawValue {
            DispatchQueue.main.async {
                if self.detectedBarcode == nil {
                    self.detectedBarcode = rawValue
                }
            }
        }
    }
}

This converts each live frame into a VisionImage for ML Kit to analyze. If ML Kit finds a barcode, it publishes the value to SwiftUI.

Camera setup: live video feed

struct CameraView: UIViewRepresentable {
    @ObservedObject var scanner: BarcodeScanner

CameraView bridges UIKit’s AVFoundation camera into SwiftUI.

Creating the camera session

func makeUIView(context: Context) -> UIView {
    let view = UIView(frame: .zero)
    let captureSession = AVCaptureSession()
    captureSession.sessionPreset = .high

This sets up a high-quality camera session for live preview.

Adding input and output

guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
      let videoInput = try? AVCaptureDeviceInput(device: videoDevice),
      captureSession.canAddInput(videoInput) else { return view }
captureSession.addInput(videoInput)

let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(context.coordinator, queue: DispatchQueue(label: "videoQueue"))
captureSession.addOutput(videoOutput)

This adds the camera input and video output. Each frame is sent to the coordinator, which triggers barcode scanning.

Showing the camera preview

let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)

This displays the live camera feed as the background.

Coordinator, forwarding frames to ML Kit

class Coordinator: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
    var scanner: BarcodeScanner
    init(scanner: BarcodeScanner) { self.scanner = scanner }

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        scanner.scanBarcode(from: sampleBuffer)
    }
}

This code acts as a bridge between the camera feed and ML Kit – every frame is analyzed in real time.

Step 4: Embed the barcode scanner view

Next, open the ContentView.swift file located in the same folder and replace its content with the following code:

import SwiftUI

struct ContentView: View {
    var body: some View {
        BarcodeScannerView()
    }
}

Step 5: Configure camera permissions

Finally, you’ll need to ensure the app can access the device camera.

In Xcode, select your project in the project navigator, select the target once more in the project and targets list, and open the Info tab. Add a new key “Privacy – Camera Usage Description” and provide a value, e.g., “Grant camera access to scan barcodes”.

Alternatively, you can directly edit the project’s Info.plist file.

<key>NSCameraUsageDescription</key>
<string>Grant camera access to scan barcodes</string>

Your barcode scanner is now ready! Build the app and run it on your iOS device to scan some barcodes.

Scanning a barcode with Google ML Kit on iOS

Conclusion

This concludes our tutorial on how to use Google ML Kit to build a barcode scanner app for iOS.

Free solutions like this one can be great for prototyping and personal projects. However, Google doesn’t offer enterprise support for ML Kit, so companies relying on it for their scanning needs won’t be able to submit feature requests nor count on help when things don’t work as expected.

We developed the Scanbot Barcode Scanner SDK to help companies overcome these hurdles. Our goal was to provide a developer-friendly solution for a wide range of platforms that consistently delivers high-quality results, even in challenging circumstances – enterprise-grade support included.

In the following tutorial, we’ll show you how to set up a barcode scanning app using the Scanbot iOS Barcode Scanner SDK.

Building an iOS barcode scanner app with the Scanbot SDK

To set up our app, we’ll follow these steps:

  1. Preparing the project
  2. Setting up the app entry point
  3. Implementing the barcode scanner view

Thanks to the SDK’s Ready-to-Use UI Components, we’ll have an intuitive user interface out of the box.

Scanning a single QR code with our SwiftUI Barcode Scanner app
Scanning a single QR code
Scanning multiple barcodes using the AR overlay with our SwiftUI Barcode Scanner app
Scanning multiple barcodes with the AR overlay

Step 1: Prepare the project

Open Xcode and create a new iOS App project. Name the project (e.g., “SwiftUI Barcode Scanner”), choose SwiftUI as the interface, and Swift as the language.

After opening your project, go to File > Add Package Dependencies… and add the Scanbot Barcode Scanner SDK package for the Swift Package Manager.

Now open your Main App Target’s Info tab and add a Privacy – Camera Usage Description key with a value such as “Grant camera access to scan barcodes”.

Step 2: Set up the app entry point

Open SwiftUI_Barcode_ScannerApp.swift, import the Scanbot Barcode Scanner SDK, and implement the init() method to set the SDK’s license key.

import SwiftUI

// Import the SDK
import ScanbotBarcodeScannerSDK

@main
struct iOS_Swift_UI_Barcode_ScannerApp: App {

    // Set the license key
    init() {
        //Scanbot.setLicense("<YOUR_LICENSE_KEY_HERE>")
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

If you have a license key, you can uncomment the code and replace <YOUR_LICENSE_KEY_HERE> with your actual key. Otherwise, leave the code as is. The SDK will then run in trial mode for 60 seconds per session.

Step 3: Implement the barcode scanner view

Open ContentView.swift and once again import the SDK.

import ScanbotBarcodeScannerSDK

Then, inside struct ContentView: View {} and above var body: some View {}, add the configuration for single-barcode scanning.

let configuration: SBSDKUI2BarcodeScannerScreenConfiguration = {
    let config = SBSDKUI2BarcodeScannerScreenConfiguration()
    let usecase = SBSDKUI2SingleScanningMode()
    config.useCase = usecase
    return config
}()

Then replace the boilerplate code inside var body: some View {} with the barcode scanner view and pass the configuration.

var body: some View {
    SBSDKUI2BarcodeScannerView(
        configuration: configuration,
        onSubmit: { result in 
            // Handle the scanned barcode results
        },
        onCancel: {
            // Handle the user tapping the 'Cancel' button
        },
        onError: { error in
            // Handle errors
        }
    )
}

In this tutorial, we’re going to print the format and value of the first barcode recognized in the camera stream to the console.

onSubmit: { (result: SBSDKUI2BarcodeScannerUIResult?) in
    if let result = result {
        guard let barcodeItem = result.items.first else {
            print("No barcode found")
            return
        }
        let format = barcodeItem.barcode.format.name
        let value = barcodeItem.barcode.textWithExtension
        print("\(format): \(value)")
    }
},

To make sure you only scan the barcode you want, you can make use of the confirmation dialogue included in the SDK’s RTU UI components. To use it, simply enable the required component in the usecase configuration.

    let configuration: SBSDKUI2BarcodeScannerScreenConfiguration = {
        let config = SBSDKUI2BarcodeScannerScreenConfiguration()
        let usecase = SBSDKUI2SingleScanningMode()

        // Enable the confirmation dialogue
        usecase.confirmationSheetEnabled = true

        config.useCase = usecase
        return config
    }()

Now our iOS Barcode Scanner app is ready. Build and run the app and give it a try!

Scanning a single QR code with our SwiftUI Barcode Scanner app

Optional: Implement multi-barcode scanning with an AR overlay

As it is now, our app prints the value of the first scanned barcode to the console. But we can also scan multiple barcodes at once. As an added bonus, we can also display the barcodes’ values right in the scanning screen while giving users the option to tap on any barcode to add it to a results sheet.

To achieve this, simply replace the single-barcode scanning configuration in ContentView.swift with the multi-scanning one and also enable the AR overlay:

let configuration: SBSDKUI2BarcodeScannerScreenConfiguration = {
    let config = SBSDKUI2BarcodeScannerScreenConfiguration()
    let usecase = SBSDKUI2MultipleScanningMode()
    usecase.arOverlay.visible = true
    config.useCase = usecase
    return config
}()

You can change the results handling to reflect that multiple barcodes’ values should be printed to the console:

if let result = result {
    guard !result.items.isEmpty else {
        print("No barcode found")
        return
    }
    for barcodeItem in result.items {
        let format = barcodeItem.barcode.format.name
        let value = barcodeItem.barcode.textWithExtension
        print("\(format): \(value)")
    }
}

Your final ContentView.swift will then look something like this:

import SwiftUI
import ScanbotBarcodeScannerSDK

struct ContentView: View {
    let configuration: SBSDKUI2BarcodeScannerScreenConfiguration = {
        let config = SBSDKUI2BarcodeScannerScreenConfiguration()
        let usecase = SBSDKUI2MultipleScanningMode()
        usecase.arOverlay.visible = true
        config.useCase = usecase
        return config
    }()

    var body: some View {
        SBSDKUI2BarcodeScannerView(
            configuration: configuration,
            onSubmit: { (result: SBSDKUI2BarcodeScannerUIResult?) in
                if let result = result {
                    guard !result.items.isEmpty else {
                        print("No barcode found")
                        return
                    }
                    for barcodeItem in result.items {
                        let format = barcodeItem.barcode.format.name
                        let value = barcodeItem.barcode.textWithExtension
                        print("\(format): \(value)")
                    }
                }
            },
            onCancel: {
                // Handle the user tapping the 'Cancel' button
            },
            onError: { error in
                // Handle errors
            }
        )
    }
}

#Preview {
    ContentView()
}

Now build and run the app again to test your multi-barcode scanner.

Using Google ML Kit on iOS to build a barcode scanner app with Swift

If you’re in need of some barcodes, we’ve got you covered:

Various barcodes for testing

Conclusion

🎉 Congratulations! You’ve successfully built a powerful iOS barcode scanning app in SwiftUI!

This is just one of the many scanner configurations the Scanbot SDK has to offer – take a look at the RTU UI documentation and API references to learn more.

Should you have questions or run into any issues, we’re happy to help! Just shoot us an email via tutorial-support@scanbot.io.

Happy scanning! 🤳

Related blog posts

Experience our demo apps

Barcode Icon Art

Barcode Scanner SDK

Scan 1D and 2D barcodes reliably in under 0.04s. Try features like Batch Scanning, Scan & Count, and our AR Overlays.

Launch Web Demo

Scan the code to launch the web demo on your phone.

Web QR Code

Also available to download from:

Document Icon Art

Document Scanner SDK

Scan documents quickly and accurately with our free demo app. Create crisp digital scans in seconds.

Launch Web Demo

Scan the code to launch the web demo on your phone.

Black and white QR code. Scan this code for quick access to information.

Also available to download from:

Data_capture Icon Art

Data Capture Modules

Try fast, accurate data capture with our demo app. Extract data from any document instantly – 100% secure.

Launch Web Demo

Scan the code to launch the web demo on your phone.

Black and white QR code. Scan this quick response code with your smartphone.

Also available to download from: