The skip-qrcode framework is available at https://github.com/skiptools/skip-qrcode.git, which can be checked out and tested with skip test once Skip is installed.
SkipQRCode
- Installation
- Quick Start
- Platform-Specific Behavior
- API Reference
- Supported Barcode Types
- Architecture
- Requirements
- Building
- Testing
- Troubleshooting
- Contributing
- Credits
This is a free Skip Swift/Kotlin library project containing the following modules:
SkipQRCode
Installation
Add SkipQRCode as a dependency in your Package.swift:
dependencies: [
.package(url: "https://source.skip.tools/skip-qrcode.git", "0.0.1"..<"2.0.0")
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "SkipQRCode", package: "skip-qrcode")
]
)
]
Quick Start
Basic Usage
import SwiftUI
import SkipQRCode
struct ContentView: View {
@State private var showScanner = false
@State private var scannedCode: String?
var body: some View {
VStack(spacing: 20) {
Button("Scan QR Code") {
showScanner = true
}
.buttonStyle(.borderedProminent)
if let code = scannedCode {
Text("Scanned: \(code)")
.font(.headline)
}
}
.sheet(isPresented: $showScanner) {
#if os(iOS)
BarcodeScannerView { code in
scannedCode = code
showScanner = false
}
.ignoresSafeArea()
#elseif os(Android)
// Android scanner launches as a native activity
Color.clear
.onAppear {
showScanner = false
AndroidBarcodeScanner.scan { code in
if let code = code {
scannedCode = code
}
}
}
#endif
}
}
}
Advanced Example with Error Handling
import SwiftUI
import SkipQRCode
struct AdvancedScannerView: View {
@State private var showScanner = false
@State private var scannedCode: String?
@State private var scanError: String?
@State private var isScanning = false
var body: some View {
VStack(spacing: 20) {
Text("QR Code Scanner")
.font(.title)
if let code = scannedCode {
VStack(alignment: .leading, spacing: 8) {
Text("Scanned Code:")
.font(.caption)
.foregroundColor(.secondary)
Text(code)
.font(.body)
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
.padding()
}
if let error = scanError {
Text("Error: \(error)")
.foregroundColor(.red)
.padding()
}
Button(action: startScanning) {
HStack {
Image(systemName: "qrcode.viewfinder")
Text(isScanning ? "Scanning..." : "Scan QR Code")
}
}
.buttonStyle(.borderedProminent)
.disabled(isScanning)
}
.padding()
.sheet(isPresented: $showScanner) {
#if os(iOS)
BarcodeScannerView { code in
handleScanResult(code)
}
.ignoresSafeArea()
#elseif os(Android)
Color.clear
.onAppear {
showScanner = false
performAndroidScan()
}
#endif
}
}
private func startScanning() {
scannedCode = nil
scanError = nil
isScanning = true
showScanner = true
}
private func handleScanResult(_ code: String) {
scannedCode = code
showScanner = false
isScanning = false
}
#if os(Android)
private func performAndroidScan() {
AndroidBarcodeScanner.scan { code in
isScanning = false
if let code = code {
scannedCode = code
} else {
scanError = "Scan cancelled or failed"
}
}
}
#endif
}
Platform-Specific Behavior
iOS
Implementation: Uses VisionKit’s DataScannerViewController
- Requires iOS 17.0+
- Full-screen camera interface
- Automatic barcode detection
- Built-in guidance overlay
- Pinch-to-zoom support
- High frame rate tracking
Permissions: Add camera usage description to your Info.plist:
<key>NSCameraUsageDescription</key>
<string>We need camera access to scan QR codes and barcodes</string>
Android
Implementation: Uses Google ML Kit with CameraX
- Requires Android API 24+
- Native activity-based scanner
- ML Kit barcode detection
- Torch/flashlight control
- Portrait orientation
Permissions: Automatically merged from the package’s AndroidManifest.xml:
android.permission.CAMERA- Camera hardware features (optional)
Activities: Scanner activities are automatically registered:
skip.qrcode.MLKitScanActivity- Main scanner with cameraskip.qrcode.ScanHostActivity- Activity launcher/bridge
API Reference
AndroidBarcodeScanner
A utility struct for launching the Android scanner.
public struct AndroidBarcodeScanner {
public static func scan(completion: @escaping @Sendable (String?) -> Void)
}
Parameters:
completion: Async callback invoked when scanning completes. Receives:- The scanned barcode string if successful
nilif the scan was cancelled or failed
Usage:
AndroidBarcodeScanner.scan { result in
if let barcode = result {
print("Scanned: \(barcode)")
} else {
print("Scan cancelled or failed")
}
}
Supported Barcode Types
SkipQRCode supports all common 1D and 2D barcode formats:
- 2D Codes: QR Code, Data Matrix, Aztec, PDF417
- 1D Codes: UPC-A, UPC-E, EAN-8, EAN-13
- Other: Code 39, Code 93, Code 128, ITF, Codabar
- Specialized: Driver’s License, Calendar Events, Contact Info
Architecture
SkipQRCode is a transpiled Skip package with bridging support, which means:
- ✅ iOS: Direct Swift → VisionKit (native iOS framework)
- ✅ Android: Swift → Transpiled Kotlin → ML Kit + CameraX
- ✅ Bridging: Automatically bridges to native SkipFuse apps
- ✅ Platform Optimized: Uses best-in-class libraries for each platform
- ✅ Zero Setup: Import once, works on both platforms
Component Overview
iOS Side:
SwiftUI → VisionKit DataScannerViewController
Android Side (Transpiled):
Swift → AndroidBarcodeScanner.swift
↓ (transpiles to)
Kotlin → ScanHostActivity → MLKitScanActivity
(activity trampoline) (CameraX + ML Kit scanner)
How It Works
- On iOS: Uses Apple’s VisionKit for native barcode scanning
- On Android:
- Swift code transpiles to Kotlin
- Launches native Android Activities with ML Kit
- Camera preview rendered programmatically (no XML resources)
- Results passed back via polling mechanism
Requirements
- iOS: 17.0 or later
- Android: API 24 (Android 7.0) or later
- Skip: 1.6.27 or later
- Xcode: 15.0 or later
Building
This project uses the Skip plugin with native compilation mode.
Install Skip using Homebrew:
brew install skiptools/skip/skip
Build the package:
swift build
Testing
Run tests using:
swift test
For parity testing across platforms:
skip test
Troubleshooting
iOS
Scanner doesn’t appear:
- Ensure you’re running on iOS 17.0 or later
- Check that
NSCameraUsageDescriptionis in your Info.plist - Verify camera permissions are granted
Scanner shows but doesn’t scan:
- Check that
DataScannerViewController.isSupportedreturns true - Ensure good lighting conditions
- Try different barcode types
Android
Scanner crashes on launch:
- Verify all dependencies are properly resolved
- Check that camera permissions are declared in manifest
- Ensure device has a working camera
Scanned results not returning:
- Check LogCat for error messages (tag:
MLKitScanActivity,ScanHostActivity) - Ensure ML Kit dependencies are included
- Verify network connectivity (ML Kit may download models)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Development Setup
- Clone the repository
- Install Skip:
brew install skiptools/skip/skip - Open in Xcode or your preferred IDE
- Make your changes
- Run tests:
swift testorskip test - Submit a PR
Credits
Built with Skip - Swift for iOS and Android
Technologies Used:
- iOS: VisionKit, SwiftUI
- Android: ML Kit, CameraX, Kotlin, Jetpack Compose