Folks using the camera on iOS 11 may have discovered that it now allows you to scan QR codes without any additional apps. Just point the camera app at a QR code, and you get a pop up asking you if you want to visit the code’s URL in Safari.
While this is a nifty shortcut, it highlights powerful bar code scanning capabilities that have been built into iOS since version 7. Using AVFoundation, you can not only scan QR codes but quite a large variety of industry bar code formats. This is good news, because it greatly expands the use case of this technology beyond the once ubiquitous QR codes that proliferated early the mobile phone era.
This article will show you how easy it is to add bar code scanning to your app. You can follow along below or get a head start by cloning the repo here.
But first a little Q&A:
What kind of bar codes can AVFoundation scan?
Almost anything. Support includes a wide variety of bar code formats (both 1-dimensional ones like supermarket UPC codes as well as newer 2-dimensional variants) as well as QR codes. You can see a full list of the types in the AVMetadataObject.ObjectType enumeration.
You can scan codes in the supermarket, scan airline or train tickets, or almost anything that is designed to be machine readable.
How well does it work?
The ability do detect and decode the bar codes is quite good, but there are some caveats:
If you hold the camera at an angle relative to the bar code, iOS may not detect it.
In my testing, holding the phone in landscape mode (the same orientation as the bar codes I was scanning) provided faster detection.
The speed at which it detects partly depends on how quickly the camera on your iOS device brings the bar code image into focus. If you hold the camera too close to the bar code (e.g. an inch or less), it may never come into focus at all.
Lighting conditions greatly affect the success rate. Brighter light is better.
With practice holding the camera and in good lighting conditions, I have found that I can typically scan a bar code in one to three seconds. Just don’t expect the speed and successful detection rate to be as high as a dedicated hardware laser scanner.
Setting it up
To set this up, you use the same AVCaptureSession class that has been around since iOS 4 to take pictures and videos.
You then use the AVCaptureDevice class to find the camera you want to use (usually the rear camera), and attach it to the captureSession Like this:
You next add a hook to extract metadata from the video. This uses the AVCaptureMetadataOutput class. It has a delegate method you use to return the captured bar code data.
The delegate callback comes in the AVCaptureMetadataOutputObjectsDelegate
protocol. It only has one method that looks like below in Swift 4. (Caution: If using Swift 3.x the delegate method is different, so it won’t get called if defined like below.)
You then tell the AVCaptureMetadataOutput which bar code types you want to capture. For our example, we’ll try to capture every type it knows about.
Finally, you start the capture session with:
The above steps are enough to get callbacks to the delegate method every time the camera points to a recognized bar code. However, a few things are missing:
On iOS, you must get special permission for an app to access the camera, otherwise the app will crash or fail to get access to the camera device.
When using the app, you can’t see what you are pointing at, because we aren’t displaying the camera view.
We don’t do anything with the results of the scans.
Let’s tackle those issues one at a time, starting with the permissions issue. Without that solved, we can’t even run the app!
Before anything else, you must add a new key/value into the app’s Info.plist file. This entry declares that the app wants to use the camera and supplies the user with a prompt message explaining why.
Before trying to access the camera, you check to see if this permission has been granted already, and if not, ask the user for it:
In the code above, I set a couple of flags called accessDenied and accessRequested, so we can use these later to know what is going on if we cannot get access to the camera. We can then present a dialog to the user explaining why. Since this isn’t core to this exercise, I won’t show the details here. But you can see how the dialogs are presented in the full code in “Extra credit section 1”.
The setupCapture method defined referenced above will have the full code needed to set up the bar code capture. This code must include this permissions checking, but we will recursively call it again after the permission is granted so we can set up the capture again if the user authorized camera access.
With that done, we can move on to the second missing item, showing on the screen what the camera is seeing. Doing this is pretty simple. We construct a videoPreviewLayer with the capture session, and make it a subview of our view. Like this:
With this in place, our full setupCapture method looks like this:
Our code currently doesn’t do anything if it does not find a bar code. The simplest solution is to show a dialog that displays the text encoded in the barcode. If the text is a URL (as is typically true with a QR code), then we can add a button on the dialog to launch it in Safari. Here’s the code that does that:
The code above is defined in its own methods and not called from the delegate callback. You can hook that in for Swift 3 or 4 like this:
If you put all of this in a ViewController, you’ll now have a fully functioning bar code scanning app. You can try this yourself with the full project on Github here. The project includes all the code above plus a few other little features that are a bit peripheral to the subject to discuss in detail:
The app draws a crosshair overlay on top of the video to give guidance of where to aim the camera. (See extra credit section 2)
The app draws an outline around the scanned bar code area right after it is detected. (See extra credit section 3)
Adding a bar code scanner to an iOS app is quite simple and the camera works surprisingly well. Now that iOS supports scanning Bluetooth Beacons, scanning NFC tags and scanning bar codes, you have many options for making your app responsive to objects around you.