QRCode Generation and Scanning on iOS 13
How to generate a QR code image and how to scan a QR code with camera on iOS
Generating QRCodes using CoreImage
iOS has built in support for generating several different types of 1D and 2D bar codes.
The API in the CoreImage
framework used to generate these codes is CIFilter
, which returns a CIImage
. We can then turn this into a UIImage
to display on screen otherwise use.
The filter is given an NSData
object as the input, and a string representing the amount of error correction to be used when making the QRCode (L
7%, M
15%, Q
25%, or H
30%).
let qrFilter = CIFilter(name: "CIQRCodeGenerator")
// Text
qrFilter?.setValue(text.data(using: .isoLatin1), forKey: "inputMessage")
// Error correction
qrFilter?.setValue("M", forKey: "inputCorrectionLevel")
return qrFilter?.outputImage
CIFilter *qrFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
// Set the QR Code contents
[qrFilter setValue:[qrString dataUsingEncoding:NSISOLatin1StringEncoding] forKey:@"inputMessage"];
// Error correction
[qrFilter setValue:@"M" forKey:@"inputCorrectionLevel"];
return qrFilter.outputImage;
One small oddity is that, unlike might be expected, the string is not encoded with UTF8, but instead ISO Latin 1
string encoding.
Scaling
The resulting image is a QRCode, but is only as big as it needs to be so when you put it in your image view it will be scaled up and look blurry. So before using it you likely want to scale up the CIImage
to the size of the image view that it will be displayed in.
let qrCode:CIImage = createQRCode()
let viewWidth = imageView.bounds.size.width;
let scale = viewWidth/qrCode.extent.size.width;
let scaledImage = qrCode.transformed(by: CGAffineTransform(scaleX: scale, y: scale))
imageView.image = UIImage(ciImage: scaledImage)
CIImage *qrCode = [self createQRForString:contents];
CGFloat viewWidth = self.imageView.bounds.size.width;
CGFloat scale = viewWidth/qrCode.extent.size.width;
CIImage *scaledImage = [qrCode imageByApplyingTransform:CGAffineTransformMakeScale(scale, scale)];
self.imageView.image = [UIImage imageWithCIImage:scaledImage];
Reading QRCodes with AVFoundation Metadata
Reading a QRCode is similarly easy on iOS. When setting up a camera capture session (getting output from the camera) you can also attach various metadata outputs, for things such as faces, certain objects and QRCodes.
After getting a configured AVCaptureSession
we can add our metadata output.
let metadataOutput = AVCaptureMetadataOutput()
if (captureSession.canAddOutput(metadataOutput)) {
captureSession.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
metadataOutput.metadataObjectTypes = [.qr]
}
AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
if ([captureSession canAddOutput:metadataOutput]) {
[captureSession addOutput:metadataOutput];
[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
metadataOutput.metadataObjectTypes = @[AVMetadataObjectTypeQRCode];
}
Then we just need to implement AVCaptureMetadataOutputObjectsDelegate
and we have the QRCode's contents
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
if let metadataObject = metadataObjects.first {
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
guard let stringValue = readableObject.stringValue else { return }
print("QR Code: \(stringValue)")
}
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
AVMetadataObject *metadata = [metadataObjects firstObject];
if ([metadata isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) {
NSString *stringValue = [(AVMetadataMachineReadableCodeObject *)metadata stringValue];
NSLog(@"QR Code: %@", stringValue);
}
}
You can also ask the camera preview layer to translate the detected object into it's local coordinate system, allowing you to overlay something on the camera view that matches the QRCode's position.
let qrCodeObject = previewLayer.transformedMetadataObject(for: readableObject)
showQRCodeBounds(frame: qrCodeObject?.bounds)
QRCode Generator and Scanner Example Project
The example code in this blog post comes from an example project on Github, which I wrote to demonstrate how to generate a QR code image and how to scan a QR code with the devices camera. (There is also a less feature complete ObjC example project)
GeneratorViewController.swift | ReaderViewController.swift |
---|---|
Project Structure
The Xcode project uses the tab based application template, with the application and scene delegates unchanged from the Xcode template.
The 2 main files are GeneratorViewController.swift
and ReaderViewController.swift
and the code inside is very similar to the included example code in this blog post, just with the extra surrounding code needed to glue it together into a usable application.