Generating QR Codes in Swift 4

Dominic Holmes
5 min readJul 19, 2018

--

Check out pennlabs.org!

Starting with iOS 11, every iPhone user has the ability to easily scan QR codes. This is a big deal! Users no longer have to download specialized apps — any time the camera detects a code, it prompts the user to open the code in Safari. And with universal linking, you can even use these codes to open apps!

This makes QR a really smooth way to pass data between users who are shoulder-to-shoulder —companies like Facebook, Snapchat, and Spotify are already using custom QR-like solutions for sharing features.

Not-quite QR (from Spotify).

The tech is useful, accessible, and makes sense to your users. Unfortunately, there’s no simple way to generate them in Swift 4 (and it gets even weirder if you want to customize them).

But fear not! This article covers the basics of programmatically generating QR codes. Beyond that, I’ll show you how to edit them —something I didn’t see in other tutorials.

Let’s get started!

Core Image and You

QR codes are generated in Swift using the Core Image framework with CIFilters. Core Image is used (as the name suggests) for processing images — mostly applying filters and effects to photos. It also has the function of generating QR codes, barcodes, and other useful visual effects (full list here).

So! To generate the CIImage of your QR code:

  1. Get the String you want to encode
  2. Get an NSData object (ascii-encoded) from that String
  3. Initialize a CIFilter with the QR code preset
  4. Add your data to the filter
  5. Generate a CIImage from the filter. This image (with a little processing) can be put into a UIImageView and displayed to users.
// import UIKit// 1
let myString = "https://pennlabs.org"
// 2
let data = myString.data(using: String.Encoding.ascii)
// 3
guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else { return }
// 4
qrFilter.setValue(data, forKey: "inputMessage")
// 5
guard let qrImage = qrFilter.outputImage else { return }
Why so blurry?

Unfortunately, the generated code will have a white (not transparent) background and black encoding. It will also be blurry if scaled up, because the generated CIImage is very small.

In my application I needed a large code with white foreground and transparent background, so that it looked nice against a dark-colored background (if you don’t care about changing the colors, skip to the end).

Scaling the code is easy, but editing the colors is a little more involved.

Scaling the Code

To scale up the code without losing resolution, use a CGAffineTransform. I needed a factor of 10 for a full-screen code, your mileage may vary.

let transform = CGAffineTransform(scaleX: 10, y: 10)
let scaledQrImage = qrImage.transformed(by: transform)
Much better!

Changing the Colors

To get a white encoding with transparent background:

  • Invert the colors with “CIColorInvert” filter
  • Make the now-black background transparent with “CIMaskToAlpha” filter
  • Create a CGImage with the generated CIImage, taking the “extent” into account

These filters are a little different from the “CIQRCodeGenerator” filter. Instead of taking input NSData and returning a CIImage, they take a CIImage as input and apply filters to that image.

This means we can chain together CIFilters, using the output from the previous filter as the input of the next.

First, we invert the colors:

// Create the filterguard let colorInvertFilter = CIFilter(name: "CIColorInvert") else { return }// Set the input image to what we generated abovecolorInvertFilter.setValue(scaledQrImage, forKey: "inputImage")// Get the output CIImageguard let outputInvertedImage = colorInvertFilter.outputImage else { return }

Next, we replace all black with transparency:

// Create the filter
guard let maskToAlphaFilter = CIFilter(name: "CIMaskToAlpha") else { return }
// Set the input image to the colorInvertFilter output
maskToAlphaFilter.setValue(outputInvertedImage, forKey: "inputImage")
// Get the output CIImage
guard let outputCIImage = maskToAlphaFilter.outputImage else { return }

Processing the Image

Finally, we have to do some processing to convert the CIImage to the UIImage. Do this even if you skip the changing color steps:

// Get a CIContext
let context = CIContext()
// Create a CGImage *from the extent of the outputCIImage*
guard let cgImage = context.createCGImage(outputCIImage, from: outputCIImage.extent) else { return }
// Finally, get a usable UIImage from the CGImage
let processedImage = UIImage(cgImage: cgImage)
Beautiful!

That’s it! We now have a high-resolution QR code with white encoding and a transparent background — the full code for both edited & vanilla codes is below.

If you’re having trouble getting your code to work, make sure there’s enough contrast between the foreground and background. I’m pushing it a bit with the one above.

If this helped you / confused you /made you furious because you know a better way — please leave a comment down below!

Thanks for reading :~)

Full Code

Here’s the code for a white foreground and transparent background:

// import UIKit// Get define string to encode
let myString = "https://pennlabs.org"
// Get data from the string
let data = myString.data(using: String.Encoding.ascii)
// Get a QR CIFilter
guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else { return }
// Input the data
qrFilter.setValue(data, forKey: "inputMessage")
// Get the output image
guard let qrImage = qrFilter.outputImage else { return }
// Scale the image
let transform = CGAffineTransform(scaleX: 10, y: 10)
let scaledQrImage = qrImage.transformed(by: transform)
// Invert the colors
guard let colorInvertFilter = CIFilter(name: "CIColorInvert") else { return }
colorInvertFilter.setValue(scaledQrImage, forKey: "inputImage")
guard let outputInvertedImage = colorInvertFilter.outputImage else { return }
// Replace the black with transparency
guard let maskToAlphaFilter = CIFilter(name: "CIMaskToAlpha") else { return }
maskToAlphaFilter.setValue(outputInvertedImage, forKey: "inputImage")
guard let outputCIImage = maskToAlphaFilter.outputImage else { return }
// Do some processing to get the UIImage
let context = CIContext()
guard let cgImage = context.createCGImage(outputCIImage, from: outputCIImage.extent) else { return }
let processedImage = UIImage(cgImage: cgImage)

And here’s the code for a black foreground and white background:

// import UIKit// Get define string to encode
let myString = "https://pennlabs.org"
// Get data from the string
let data = myString.data(using: String.Encoding.ascii)
// Get a QR CIFilter
guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else { return }
// Input the data
qrFilter.setValue(data, forKey: "inputMessage")
// Get the output image
guard let qrImage = qrFilter.outputImage else { return }
// Scale the image
let transform = CGAffineTransform(scaleX: 10, y: 10)
let scaledQrImage = qrImage.transformed(by: transform)
// Do some processing to get the UIImage
let context = CIContext()
guard let cgImage = context.createCGImage(scaledQrImage, from: scaledQrImage.extent) else { return }
let processedImage = UIImage(cgImage: cgImage)

--

--