Scanning a QR code – or indeed any kind of visible code such as barcodes – can be done by Apple’s AVFoundation library. This doesn’t integrate into SwiftUI terribly smoothly, so to skip over a whole lot of pain I’ve packaged up a QR code reader into a Swift package that we can add and use directly inside Xcode.
My package is called CodeScanner, and it's available on GitHub under the MIT license at https://github.com/twostraws/CodeScanner – you’re welcome to inspect and/or edit the source code if you want. Here, though, we’re just going to add it to Xcode by following these steps:
The CodeScanner package gives us one CodeScannerView
SwiftUI view to use, which can be presented in a sheet and handle code scanning in a clean, isolated way. I know I keep repeating myself, but I hope you can see the continuing theme: the best way to write SwiftUI is to isolate functionality in discrete methods and wrappers, so that all you expose to your SwiftUI layouts is clean, clear, and unambiguous.
We already have a “Scan” button in ProspectsView
, and we’re going to use that trigger QR scanning. So, start by adding this new @State
property to ProspectsView
:
@State private var isShowingScanner = false
Earlier we added some test functionality to the “Scan” button so we could insert some sample data, but we don’t need that any more because we’re about to scan real QR codes. So, replace the action code for the toolbar button with this:
isShowingScanner = true
When it comes to handling the result of the QR scanning, I’ve made the CodeScanner package do literally all the work of figuring out what the code is and how to send it back, so all we need to do here is catch the result and process it somehow.
When the CodeScannerView
finds a code, it will call a completion closure with a Result
instance either containing details about the code that was found or an error saying what the problem was – perhaps the camera wasn’t available, or the camera wasn’t able to scan codes, for example. Regardless of what code or error comes back, we’re just going to dismiss the view; we’ll add more code shortly to do more work.
Start by adding this new import near the top of ProspectsView.swift:
import CodeScanner
Now add this method to ProspectsView
:
func handleScan(result: Result<ScanResult, ScanError>) {
isShowingScanner = false
// more code to come
}
Before we show the scanner and try to handle its result, we need to ask the user for permission to use the camera:
And now we’re ready to scan some QR codes! We already have the isShowingScanner
state that determines whether to show a code scanner or not, so we can now attach a sheet()
modifier to present our scanner UI.
Creating a CodeScannerView
takes at least three parameters:
[.qr]
is fine, but iOS supports lots of other types too.CodeScannerView
automatically presents a replacement UI so we can still test that things work. This replacement UI will automatically send back whatever we pass in as simulated data.handleScan()
method so we’ll use that.So, add this below the existing toolbar()
modifier in ProspectsView
:
.sheet(isPresented: $isShowingScanner) {
CodeScannerView(codeTypes: [.qr], simulatedData: "Paul Hudson\npaul@hackingwithswift.com", completion: handleScan)
}
That’s enough to get most of this screen working, but there is one last step: replacing the // more code to come
comment in handleScan()
with some actual functionality to process the data we found.
If you recall, the QR codes we’re generating are a name, then a line break, then an email address, so if our scanning result comes back successfully then we can pull apart the code string into those components and use them to create a new Prospect
. If code scanning failed, we’ll just print an error – you’re welcome to show some more interesting UI if you want!
Replace the // more code to come
comment with this:
switch result {
case .success(let result):
let details = result.string.components(separatedBy: "\n")
guard details.count == 2 else { return }
let person = Prospect(name: details[0], emailAddress: details[1], isContacted: false)
modelContext.insert(person)
case .failure(let error):
print("Scanning failed: \(error.localizedDescription)")
}
Go ahead and run the code now. If you’re using the simulator you’ll see a test UI appear, and tapping anywhere will dismiss the view and send back our simulated data.
If you’re using a real device you’ll see a permission message asking the user to allow camera use, and you grant that you’ll see a scanner view. To test out scanning on a real device, simultaneously launch the app in the simulator and switch to the Me tab – your phone should be able to scan the simulator screen on your computer.
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.