ScanKit is a simple SwiftUI package for scanning various machine-readable codes using Apple's Vision Framework.
ScanKit comes in two flavors: as a View-Model pair or as an easy to plug-in SwiftUI View.
Before using either version, you should make sure your project is capable of accessing the camera.
iOS & macOS: You must include "Privacy - Camera Usage Description", or NSCameraUsageDescription
, in your info.plist
.
macOS: "Camera" must be selected under "Resource Access" in "Hardened Runtime."
ScannerView
is a drop-in code scanner for SwiftUI that returns its results as a completion. It accepts the following parameters
symbology
: One of Apple's VNBarcodeSymbology varieties to scan for.showViewfinder
: ABoolean
indicating whether or not to display a "viewfinder" symbol over the camera preview.isScanning
: ABoolean
binding that dictates whether or not to scan the camera feed for codes. The preview will continue to be available whenfalse
, but no further results will be sent to the completion untiltrue
.
If ScannerView is wrapped within some Navigation container, it will send two buttons to the Toolbar.
- A torch button on iOS.
- A camera picker/toggle on all platforms.
On macOS the camera picker displays all available cameras, including the continuity camera. On iOS the camera toggle switches between the front and back camera.
ScannerView
's completion returns a stream of Result<String, ScanKitError>
. A ScanKitError
can be either:
notAuthorized
which indicates that camera access was not authorized.visionFailure
which indicates that initialization failed in some way. Check the log for additional details.
Minimal Example:
@State var scanning: Bool = true
ScannerView(for: .qr, isScanning: $scanning) { result in
switch result {
case .success(let string):
// To only scan once, call `self.scanning = false` here
print(string)
case .failure(let error):
print(error)
}
}
For a more custom experience, ScanKit exposes a unique View-Model combination of ScanKitPreview
and ScanKitCamera
.
ScanKitPreview
is a representable wrapper around AVCapturePreviewLayer
that can display the feed returned from ScanKitCamera
.
ScanKitCamera
interfaces directly with AVFoundation
and Vision
to handle available capture devices and Vision requests. It is an ObservableObject
that can update the view when one of its variables change.
Results are streamed from ScanKitCamera
as String
via an AsyncThrowingStream
. The stream finishes when ScanKitCamera().isScanning
is set to false
or when it encounters an error. To scan only one code at a time, simply set isScanning
to false immediately upon receipt of the first result.
The resultStream
can throw only two types of errors:
notAuthorized
which indicates that camera access was not authorized.visionFailure
which indicates that initialization failed in some way. Check the log for additional details.
Various variables and functions are available from your ScanKitCamera
object that allow you to interface with the device's camera directly from SwiftUI.
Kind | Name | Purpose |
---|---|---|
var (get) | isCapturing | A Boolean indicating whether or not the camera is active. |
var (get/set) | isScanning | A Boolean indicating whether or not the camera is scanning for machine readable codes. |
var (get) | isTorchAvailable | A Boolean indicating whether or not the current camera has a torch available. |
var (get) | isTorchOn | A Boolean indicating whether or not the torch is on. If there's no torch, this is always false. |
var (get) | isUsingBackCamera | A Boolean indicating whether or not the rear camera is active. On macOS this is always false. |
var (get) | isUsingFrontCamera | A Boolean indicating whether or not the front camera is active. On macOS this is always false. |
var (get) | resultsStream | An AsyncThrowingStream presenting the results of the scan operation. |
var (get/set) | symbology | The current code symbology being scanned for. |
var (get) | supportedSymbologies | An array of all supported symbologies. |
func | cycleCaptureDevices | Cycles through available cameras. On iOS this simply toggles between the front-facing and rear-facing cameras. |
func | getCamerasAsButtons | Returns a View containing all available cameras as individual Button . Each Button switches ScanKitCamera to that particular camera when pressed. This is best called within a Menu . |
func | toggleTorch | Toggles the torch, if one is available. |
func | start | Starts the camera. This is automatically called during appear within ScanKitPreview . |
func | stop | Stops the camera. This is automatically called during disappear within ScanKitPreview . |
Minimal Example:
@StateObject camera = ScanKitCamera()
var body: some View {
ScanKitPreview(camera: camera)
.task {
do {
for try await result in camera.resultsStream {
print(result)
}
} catch let error {
print(error)
}
}
}
Consider looking at the implementation of ScanView
for inspiration.
It should be noted up-front that the Vision framework is not the most efficient way to handle machine readable codes. However, it is cross-platform.
If you are only targeting iOS, or are planning to target macOS via Mac Catalyst, then packages like CodeScanner that utilize AVCaptureMetadataOutput would be worth considering.
SwiftUI ScanKit is available under the MIT license, which permits commercial use, modification, distribution, and private use. Feel free to use it wherever and however you want!