To run the example project, clone the repo and open Example/QRious.xcodeproj in Xcode.
- iOS 17.0+
- Swift 6.0+
Add QRious to your Package.swift dependencies:
dependencies: [
.package(url: "https://github.com/mrfour0004/QRious.git", from: "2.0.0")
]Or in Xcode, go to File > Add Package Dependencies... and enter the repository URL:
https://github.com/mrfour0004/QRious.git
import AVFoundation
import QRious
import SwiftUI
struct ContentView: View {
@State private var isScanning = true
@State private var cameraPosition: AVCaptureDevice.Position = .back
var body: some View {
AVScanner(
supportedMetadataObjectTypes: [.qr, .pdf417],
isSessionRunning: isScanning,
cameraPosition: cameraPosition
)
.focusStyle(.corners())
.onCapture { metadataObject in
print("Scanned:", metadataObject.stringValue ?? "")
}
.onError { error in
print("Scanner error:", error)
}
.ignoresSafeArea()
}
}Imports QRious and AVFoundation into your swift file.
import QRious
import AVFoundationSubclasses AVScannerViewController, which contains a scannerView and conforms to AVScannerViewDelegate. And then overrides scannerView(_:didCapture:), that's it!
class ScannerViewController: AVScannerViewController {
// ...
override func scannerView(_ scannerView: AVScannerView, didCapture metadataObject: AVMetadataMachineReadableCodeObject) {
// handle the captured metadataObject here
}
}You may also need to handle errors occur during initializing or starting the capture session:
override func scannerView(_ scannerView: AVScannerView, didFailConfigurationWithError error: Error) {
guard let scannerError = error as? AVScannerError else { return }
switch scannerError {
case .videoNotAuthorized:
print("The user didn't authorize to access the camera yet.")
case .configurationFailed:
print("This device somehow can't capture videos.")
}
}
func scannerView(_ scannerView: AVScannerView, didFailStartingSessionWithError error: Error) {
// ...handle the error here
}If you want to customize your view controller without using AVScannerViewController, you can simply add an instance of AVScannerView into your customized view controller by yourself.
class MyScannerViewController: UIViewController {
let scannerView = AVScannerView() // you can also use an @IBOutlet here.
override func viewDidLoad() {
super.viewDidLoad()
// setup your scanner view here or layout it with auto-layout in the storyboard.
}
}Then you can decide when to initialize the capture session, for example, you might want to present another view to describe your page and provide a button to let the user decide whether to authorize the camera access. To initialize the capture session, simply call
scannerView.initSession()There might be an error when initializing the capture session, remember to assign an object of AVScannerViewDelegate to handle that error. After the session is initialized, you can start running the capture session by calling.
scannerView.startSession()Make sure you call initSession() without error before calling startSession()
// The default value of supportedMetadataObjectTypes only includes `.qr`.
scannerView.supportedMetadataObjectTypes = [.qr, .pdf417]You can customize the focus indicator's shape (.corners or .box), idle scanning animation (.breathe, .morph, .fixed, .hiddenUntilCapture, or .hidden), and capture lock-on effect (AVScannerFocusStyle.CaptureEffect) in both SwiftUI and UIKit:
// SwiftUI
AVScanner()
.focusStyle(.corners())
// UIKit
scannerView.focusStyle = .corners()By default, capturing a barcode triggers CaptureEffect.cyberNeon (.default), which performs a two-stage spring animation:
- Spring Anticipation: Slightly expands the focus indicator (
1.11x) with a brief spring pause while blooming into a slowly flowing conic gradient stroke and dual-layer neon glow. - Spring Lock-On: Smoothly snaps onto the detected barcode's four corners with elastic spring damping.
-
Disable the capture effect completely (no pre-focus expansion, no gradient, and no outer glow — locks straight onto the barcode using a solid stroke):
AVScanner() .focusStyle(.corners(captureEffect: .none))
-
Disable only the flowing rotation (keep the static gradient stroke, outer glow, and spring anticipation):
AVScanner() .focusStyle( .corners( captureEffect: .init(flowDuration: 0) ) )
-
Keep only the spring anticipation pop without gradient or glow (maintain solid stroke color):
AVScanner() .focusStyle( .corners( captureEffect: .init(gradientColors: [], glowOpacity: 0) ) )
-
Customize colors, glow radius, and spring timing:
let customEffect = AVScannerFocusStyle.CaptureEffect( scale: 1.12, anticipationDuration: 0.28, springDamping: 0.62, gradientColors: [.systemCyan, .systemBlue, .systemPurple, .systemPink], flowDuration: 3.2, innerGlowColor: .systemCyan, innerGlowRadius: 10, outerGlowColor: .systemPurple, outerGlowRadius: 22, glowOpacity: 0.95 ) AVScanner() .focusStyle(.box(captureEffect: customEffect, lockDuration: 0.45))
For more detail, please refer to the example project and feel free to contact me.
QRious is available under the MIT license. See the LICENSE file for more info.
