Barcodes are ubiquitous in today’s world, making them an essential feature for many apps. With AVFoundation in Swift, you can create a powerful barcode scanner app. This guide will walk you through building a simple barcode scanner app, from setting up the project to implementing barcode scanning functionality.

Setting Up the Project

  1. Open Xcode and Create a New Project:
    • Select the “App” template.
    • Name your project (e.g., “BarcodeScanner”).
    • Choose Swift as the language and SwiftUI as the user interface.
  2. Configure Privacy Settings:
    • Open Info.plist.
    • Add the key NSCameraUsageDescription with a description like “We need access to your camera to scan barcodes”.

Designing the User Interface

  1. Open ContentView.swift and update the code to include a button that starts the scanning process and a text field to display the scanned barcode.
import SwiftUI

struct ContentView: View {
    @State private var isShowingScanner = false
    @State private var scannedCode = ""

    var body: some View {
        VStack {
            Text("Scanned Barcode:")
                .font(.headline)
            Text(scannedCode)
                .font(.largeTitle)
                .padding()

            Button(action: {
                isShowingScanner = true
            }) {
                Text("Start Scanning")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
        .sheet(isPresented: $isShowingScanner) {
            ScannerView(scannedCode: $scannedCode)
        }
    }
}

Creating the Scanner View

  1. Create a new Swift file named ScannerView.swift.
  2. Import the necessary frameworks and set up the UIViewControllerRepresentable to bridge UIKit with SwiftUI.

import SwiftUI
import AVFoundation

struct ScannerView: UIViewControllerRepresentable {
    @Binding var scannedCode: String

    func makeCoordinator() -> Coordinator {
        Coordinator(scannedCode: $scannedCode)
    }

    func makeUIViewController(context: Context) -> UIViewController {
        let viewController = ScannerViewController()
        viewController.delegate = context.coordinator
        return viewController
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) { }

    class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
        @Binding var scannedCode: String

        init(scannedCode: Binding<String>) {
            _scannedCode = scannedCode
        }

        func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
            if let metadataObject = metadataObjects.first {
                guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
                guard let stringValue = readableObject.stringValue else { return }
                AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
                scannedCode = stringValue
            }
        }
    }
}

Implementing the Scanner View Controller

  1. Create a new Swift file named ScannerViewController.swift.
  2. Implement the barcode scanning logic using AVFoundation.

import UIKit
import AVFoundation

class ScannerViewController: UIViewController {
    var captureSession: AVCaptureSession!
    var previewLayer: AVCaptureVideoPreviewLayer!
    var delegate: AVCaptureMetadataOutputObjectsDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .black
        captureSession = AVCaptureSession()

        guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
        let videoInput: AVCaptureDeviceInput

        do {
            videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
        } catch {
            return
        }

        if (captureSession.canAddInput(videoInput)) {
            captureSession.addInput(videoInput)
        } else {
            failed()
            return
        }

        let metadataOutput = AVCaptureMetadataOutput()

        if (captureSession.canAddOutput(metadataOutput)) {
            captureSession.addOutput(metadataOutput)

            metadataOutput.setMetadataObjectsDelegate(delegate, queue: DispatchQueue.main)
            metadataOutput.metadataObjectTypes = [.ean8, .ean13, .pdf417, .qr, .code128]
        } else {
            failed()
            return
        }

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

        captureSession.startRunning()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        if (captureSession?.isRunning == false) {
            captureSession.startRunning()
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        if (captureSession?.isRunning == true) {
            captureSession.stopRunning()
        }
    }

    func failed() {
        let alertController = UIAlertController(title: "Scanning not supported", message: "Your device does not support scanning a code from an item. Please use a device with a camera.", preferredStyle: .alert)
        alertController.addAction(UIAlertAction(title: "OK", style: .default))
        present(alertController, animated: true)
        captureSession = nil
    }

    override var prefersStatusBarHidden: Bool {
        return true
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return .portrait
    }
}

Connecting Everything

  1. Update ContentView.swift to present ScannerView when the button is tapped.

struct ContentView: View {
    @State private var isShowingScanner = false
    @State private var scannedCode = ""

    var body: some View {
        VStack {
            Text("Scanned Barcode:")
                .font(.headline)
            Text(scannedCode)
                .font(.largeTitle)
                .padding()

            Button(action: {
                isShowingScanner = true
            }) {
                Text("Start Scanning")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
        .sheet(isPresented: $isShowingScanner) {
            ScannerView(scannedCode: $scannedCode)
        }
    }
}

Testing the App

  1. Build and run the app on a physical device (as the simulator does not support camera usage).
  2. Tap the “Start Scanning” button and scan a barcode with the camera.
  3. The scanned barcode should appear on the screen.

In this tutorial, we’ve created a simple barcode scanner app using AVFoundation in Swift. This app includes:

  • Setting up a new project with the necessary permissions.
  • Designing the UI with SwiftUI.
  • Implementing the barcode scanning functionality using AVFoundation.
  • Bridging UIKit with SwiftUI to create a seamless user experience.

This basic app can be expanded with additional features like storing scanned barcodes, integrating with a database, or enhancing the UI. By leveraging the powerful AVFoundation framework and the simplicity of SwiftUI, you can build sophisticated barcode scanning applications for various use cases.