Skip to main content

Barcode with Preview

Same scanning setup as Minimal Barcode, plus a live camera preview so the user can see what they are aiming at. The preview comes from AilaCaptureView, a UIKit view the SDK provides, wrapped for SwiftUI with UIViewRepresentable.

Folder in the download: BarcodeWithPreview/. Scheme: BarcodeWithPreview.

What you'll see​

Barcode with Preview app showing the camera feed above the last scan result
Camera preview on top, scan result below.

How it works​

The preview wrapper. AilaCaptureView is a UIView, so SwiftUI needs a UIViewRepresentable to host it. The wrapper creates the view, sets videoGravity to .resizeAspectFill so the feed fills its frame, and does nothing else. The SDK attaches the camera session to it automatically once Aila_Start() runs.

BarcodeWithPreview/AilaCaptureViewRepresentable.swift
//
// Quickstart: Barcode with Preview – AilaCaptureView integration
// Wraps AilaCaptureView (UIKit) for SwiftUI to show the camera preview.
//

import SwiftUI
import AVFoundation
import Aila

struct AilaCaptureViewRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> AilaCaptureView {
let view = AilaCaptureView()
view.videoGravity = .resizeAspectFill
return view
}

func updateUIView(_ uiView: AilaCaptureView, context: Context) {
uiView.videoGravity = .resizeAspectFill
}
}
BarcodeWithPreview/ViewModel.swift
BarcodeWithPreview/ViewModel.swift
//
// Quickstart: Barcode with Preview – ViewModel
// Same Aila configuration as Minimal Barcode (default barcode symbologies only).
//

import Foundation
import Aila

final class BarcodeWithPreviewViewModel: ObservableObject {
@Published private(set) var lastScannedData: String?
@Published private(set) var lastScannedType: String?

var hasScanResult: Bool {
lastScannedData != nil && lastScannedType != nil
}

func startScanning() {
Aila_Init()
Aila_SetConfiguration(buildConfiguration())
Aila_Start()
}

func stopScanning() {
Aila_Stop()
}

private func buildConfiguration() -> AilaConfiguration {
let config = AilaConfiguration()
config.multiScanMode = .off
config.beepVolume = .volume3

config.setCode(.typeUPC, enabled: true)
config.setCode(.typeEAN, enabled: true)
config.setCode(.typeQR, enabled: true)
config.setCode(.type128, enabled: true)
config.setCode(.typePDF417, enabled: true)

config.scanCallback = { [weak self] results in
self?.handleScanResults(results)
}

return config
}

private func handleScanResults(_ results: [AilaScanObject]?) {
guard let results = results, !results.isEmpty else { return }
let first = results[0]
let data = first.data ?? "no data"
let type = first.typeDescription() ?? "no type"

print("Scanned — type: \(type), data: \(data)")

DispatchQueue.main.async { [weak self] in
self?.lastScannedData = data
self?.lastScannedType = type
}
}
}
BarcodeWithPreview/View.swift
BarcodeWithPreview/View.swift
//
// Quickstart: Barcode with Preview – View
// Integrates AilaCaptureView (camera preview) with default barcode scanning.
//

import Foundation
import SwiftUI

struct BarcodeWithPreviewView: View {
@StateObject private var viewModel = BarcodeWithPreviewViewModel()

var body: some View {
VStack(spacing: 0) {
Text("Barcode with Preview")
.font(.headline)
.padding(.top, 8)

AilaCaptureViewRepresentable()
.aspectRatio(4/3, contentMode: .fit)
.clipped()
.padding(.horizontal)

if viewModel.hasScanResult, let data = viewModel.lastScannedData, let type = viewModel.lastScannedType {
scanResultView(data: data, type: type)
} else {
placeholderView
}
}
.padding(.bottom)
.onAppear {
viewModel.startScanning()
}
.onDisappear {
viewModel.stopScanning()
}
}

private func scanResultView(data: String, type: String) -> some View {
VStack(alignment: .leading, spacing: 8) {
Text("Type: \(type)")
.font(.subheadline)
Text("Data: \(data)")
.font(.body)
.lineLimit(5)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color(.systemGray6))
.cornerRadius(8)
.padding(.horizontal)
.padding(.top, 16)
}

private var placeholderView: some View {
Text("Point camera at a barcode")
.foregroundStyle(.secondary)
.padding(.top, 16)
}
}
tip

You can size and position AilaCaptureView however you like. Scanning does not depend on the preview being visible or full-screen. The preview exists for the user, not the decoder.

Try changing​

  • Swap .resizeAspectFill for .resizeAspect to letterbox the feed instead of cropping it.
  • Remove the aspectRatio modifier and let the preview fill the available height.

API used​

Next: scan several codes in one session with Multi-Scan.


Want the code? The full Xcode project is available as a zip on the Quickstarts overview.