SwiftUI Integration
This guide shows you how to integrate the Anyline SDK with SwiftUI applications. The Anyline SDK is built with UIKit, but can be seamlessly integrated into SwiftUI using UIViewRepresentable.
Prerequisites
-
Anyline SDK properly set up in your iOS project (see Getting Started)
-
Basic familiarity with SwiftUI
-
iOS 13.0+ (required for SwiftUI)
Basic Integration
Step 1: Create the SwiftUI Wrapper
Create a struct that conforms to UIViewRepresentable
to wrap the Anyline ALScanView
:
import SwiftUI
import Anyline
struct AnylineScanView: UIViewRepresentable {
@Binding var scanResult: ALScanResult?
let configFileName: String
func makeUIView(context: Context) -> ALScanView {
// Load configuration
guard let config = loadScanViewConfig(configFileName) else {
fatalError("Could not load scan configuration")
}
// Create scan view
do {
let scanView = try ALScanView(frame: .zero, scanViewConfig: config)
// Set up delegate
if let plugin = scanView.viewPlugin as? ALScanViewPlugin {
plugin.scanPlugin.delegate = context.coordinator
}
// Start scanning
scanView.startCamera()
try scanView.startScanning()
return scanView
} catch {
fatalError("Failed to create scan view: \(error)")
}
}
func updateUIView(_ uiView: ALScanView, context: Context) {
// Handle updates if needed
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
Step 2: Create the Coordinator
The Coordinator handles scan results and acts as the delegate:
extension AnylineScanView {
class Coordinator: NSObject, ALScanPluginDelegate {
let parent: AnylineScanView
init(_ parent: AnylineScanView) {
self.parent = parent
}
func scanPlugin(_ scanPlugin: ALScanPlugin, resultReceived scanResult: ALScanResult) {
DispatchQueue.main.async {
self.parent.scanResult = scanResult
}
}
}
}
Step 3: Create the Configuration Loader
Add a helper function to load your scan configurations:
extension AnylineScanView {
private func loadScanViewConfig(_ fileName: String) -> ALScanViewConfig? {
guard let url = Bundle.main.url(forResource: fileName, withExtension: "json"),
let data = try? Data(contentsOf: url),
let jsonString = String(data: data, encoding: .utf8) else {
print("Error: Could not load config file \(fileName).json")
return nil
}
do {
return try ALScanViewConfig.withJSONString(jsonString)
} catch {
print("Error parsing config: \(error)")
return nil
}
}
}
Step 4: Use in Your SwiftUI View
Now you can use the scanner in any SwiftUI view:
struct ContentView: View {
@State private var scanResult: ALScanResult?
var body: some View {
ZStack {
// Scanner view
AnylineScanView(
scanResult: $scanResult,
configFileName: "barcode_config"
)
.ignoresSafeArea()
// Results overlay
if let result = scanResult {
VStack {
Spacer()
Text("Scanned: \(result.asJSONStringPretty(true))")
.padding()
.background(Color.black.opacity(0.7))
.foregroundColor(.white)
.cornerRadius(10)
.padding()
}
}
}
}
}
Common Pitfalls and Solutions
Camera and Scanning Lifecycle
Problem: The camera and scanning continue running even when the view is not visible, causing battery drain and potential crashes. Solution: Stop the camera and scanning when appropriate. Consider implementing lifecycle management through SwiftUI modifiers:
|
Error Handling
Problem: Using Better approach: Return an error view instead:
|
Navigation and Memory Management
Problem: Rapid navigation between views can cause memory leaks or scanner conflicts. Solution: Ensure proper cleanup in your coordinator’s deinit:
|
Dynamic Configuration Changes
To change scanner configuration at runtime:
struct DynamicScanView: View {
@State private var currentConfig = "barcode_config"
@State private var scanResult: ALScanResult?
var body: some View {
VStack {
// Configuration selector
Picker("Scan Mode", selection: $currentConfig) {
Text("Barcode").tag("barcode_config")
Text("MRZ").tag("mrz_config")
}
.pickerStyle(SegmentedPickerStyle())
.padding()
// Scanner (recreated when config changes)
AnylineScanView(
scanResult: $scanResult,
configFileName: currentConfig
)
.id(currentConfig) // Forces recreation when config changes
}
}
}
Using |
Troubleshooting
Scanner Not Starting
Symptoms: Black screen, no camera preview
Possible causes:
-
Missing camera permissions
-
Invalid configuration file
-
License key issues
Solutions:
-
Check camera permissions in your Info.plist:
<key>NSCameraUsageDescription</key> <string>This app uses the camera to scan documents</string>
-
Verify your configuration file is included in your app bundle
-
Check console logs for Anyline initialization errors
Memory Leaks
Symptoms: App memory usage increases over time
Solutions:
-
Ensure proper delegate cleanup
-
Avoid strong reference cycles between coordinator and parent
-
Test navigation patterns thoroughly
Best Practices
Configuration Management: Store your scan configurations as JSON files in your app bundle. This makes them easy to modify without rebuilding your app. |
State Management: Use |
Error Handling: Always handle configuration loading and scanner initialization errors gracefully to prevent app crashes. |
Testing: Test your scanner integration thoroughly on physical devices, as camera functionality doesn’t work in the simulator. |
Advanced Integration
For production applications that require more sophisticated lifecycle management, camera control, or navigation integration, consider using UIViewControllerRepresentable
instead of UIViewRepresentable
. This approach provides:
-
Automatic camera start/stop based on view controller lifecycle
-
Better memory management
-
More robust navigation integration
-
Explicit control over scanner state
This advanced approach requires creating a dedicated UIViewController
subclass to manage the scanner, but provides much better resource management for complex applications.