iTranslated by AI
Generating and Scanning QR Codes with SwiftUI
Overview
As the title suggests, I would like to try generating and reading QR codes using SwiftUI.
Since iOS 13.0 and later, the following convenient method is available, so I will use it for generation.
This method generates a QR code as an image. A QR code is a high-density matrix barcode format defined in the ISO/IEC 18004:2006 standard.
The QR code generator filter uses the following properties:
・message: A string representing the data to be encoded as a QR code as NSData
・correctionLevel: A single-character string representing the error correction format as NSString. L is 7% correction, M is 15% correction, Q is 25% correction, H is 30% correction.
Environment
- MBA M3 24GB Sonoma 14.6.1
- Xcode: Version 16.1 (16B40)
Simple Implementation
First, I would like to create a simple View that just displays a given string as a QR code.
Create QrCodeView.swift and add the following implementation.
import CoreImage.CIFilterBuiltins // ①
import SwiftUI
struct QrCodeView: View {
var data: String
var body: some View {
Image(uiImage: qrImage)
.interpolation(.none) // ②
.resizable() // ③
.scaledToFit()
.accessibilityLabel(Text("QRCode"))
}
private var qrImage: UIImage { // ④
let qrCodeGenerator = CIFilter.qrCodeGenerator()
qrCodeGenerator.message = Data(data.utf8)
qrCodeGenerator.correctionLevel = "H"
if let outputimage = qrCodeGenerator.outputImage {
if let cgImage = CIContext().createCGImage(
outputimage, from: outputimage.extent) {
return UIImage(cgImage: cgImage)
}
}
return UIImage()
}
}
#Preview {
QrCodeView(data: "abc")
.frame(width: 150, height: 150)
}
① : Importing CoreImage.CIFilterBuiltins
② : Removing interpolation when scaling the image
- Interpolation is an algorithm used to fill between pixels or display them smoothly when enlarging or shrinking an image. For QR codes, it may cause scanning errors.
③ : Making the image resizable while maintaining the aspect ratio
④ : Converting the output image to UIImage using CIFilter.qrCodeGenerator
👇 How it looks in the Preview

Converting JSON Strings to QR Codes
Next, I would like to try generating a QR code by receiving a JSON string. I will create a new file named JsonQrCodeView.swift.
First, let's define the object to be converted to JSON. Let's assume we define a Book object.
struct Book: Codable {
let id: Int
let title: String
let author: String
let publicationAt: Date
}
We will receive this Book, convert it into a JSON string, and generate a QR code.
struct JsonQrCodeView: View {
var book: Book
var body: some View {
if let json = jsonStr {
QrCodeView(data: json)
.frame(width: 150, height: 150)
} else {
Text("Qr Code Generate Error")
}
}
private var jsonStr: String? {
do {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes]
let jsonData = try encoder.encode(book)
return String(data: jsonData, encoding: .utf8)
} catch {
print(error.localizedDescription)
}
return nil
}
}
#Preview {
let book = Book(
id: 1,
title: "こころ",
author: "夏目漱石",
publicationAt: Date()
)
JsonQrCodeView(book: book)
}
👇 How it looks in the Preview

If you focus on this with an iOS camera, the JSON string should be displayed.
QR Code Scanning
Next, I would like to implement QR code scanning.
From a quick search, there seem to be about three ways to implement this:
- Use a third-party library
- A well-known one is CodeScanner
- Implement using
AVFoundation - Implement using
VisionKit'sDataScannerViewController- Only available on iOS 16 and later
In this article, I will try using VisionKit's DataScannerViewController.
Preparation
Since the camera will be used, add the Privacy - Camera Usage Description setting to Info.plist.
For the Value, just write the reason for using the camera.
- Note: For Xcode 13 and later,
Info.plistis not created by default. Please add it from "TARGETS" > "Info" tab > "Custom macOS Application Target Properties".
Also, since the camera can only be used on a physical device, you will need to configure settings to run it on an actual device.
👇 Reference URL
Implementation
Create a new file JsonQrCodeReaderView.swift with the following content.
import SwiftUI
import VisionKit // ①
struct QRCodeScanner: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> DataScannerViewController {
// ②
let dataScannerViewController = DataScannerViewController(
recognizedDataTypes: [.barcode(symbologies: [.qr])],
isHighlightingEnabled: true
)
try? dataScannerViewController.startScanning() // ③
return dataScannerViewController
}
func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) {
}
}
struct JsonQrCodeReaderView: View {
var body: some View {
QRCodeScanner()
}
}
#Preview {
JsonQrCodeReaderView()
}
① : Importing VisionKit
② : Creating DataScannerViewController
-
recognizedDataTypes
recognizedDataTypes: Set<DataScannerViewController.RecognizedDataType>- Specify
text(languages:textContentType:)orbarcode(symbologies:) - Since we are dealing with QR codes,
[.barcode(symbologies: [.qr])]is specified
- isHighlightingEnabled
- Set to
trueto highlight when a QR code is recognized
- Set to
③ : Starting the camera and beginning the scan
👇 How it looks running on an actual device

Displaying the Scanned Content
Finally, I would like to display the content of the scanned QR code on the screen.
👇 Here is the final implementation.
struct QRCodeScanner: UIViewControllerRepresentable {
// ①
var dataScannerViewController = DataScannerViewController(
recognizedDataTypes: [.barcode(symbologies: [.qr])],
isHighlightingEnabled: true
)
func makeUIViewController(context: Context) -> DataScannerViewController {
dataScannerViewController.delegate = context.coordinator
try? dataScannerViewController.startScanning()
return dataScannerViewController
}
func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) {
}
// ②
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, DataScannerViewControllerDelegate {
private let parent: QRCodeScanner
private var textView: UIView?
init(_ parent: QRCodeScanner) {
self.parent = parent
}
func dataScanner(_ dataScanner: DataScannerViewController,
didAdd addedItems: [RecognizedItem],
allItems: [RecognizedItem]
) {
guard case .barcode(let barcode) = addedItems.first else {
return
}
// ③
if let text = barcode.payloadStringValue {
let frame = CGRect(
x: barcode.bounds.topLeft.x,
y: barcode.bounds.topLeft.y,
width: abs(barcode.bounds.topRight.x - barcode.bounds.topLeft.x) + 15,
height: abs(barcode.bounds.topLeft.y - barcode.bounds.bottomLeft.y) + 15
)
let textView = UITextView(frame: frame)
textView.font = UIFont.systemFont(ofSize: 10)
textView.text = text
parent.dataScannerViewController.overlayContainerView.addSubview(textView)
self.textView = textView
}
}
// ④
func dataScanner(_ dataScanner: DataScannerViewController,
didRemove removedItems: [RecognizedItem],
allItems: [RecognizedItem]
) {
self.textView?.removeFromSuperview()
}
}
}
① : DataScannerViewController is extracted so that it can be used from the Delegate.
② : The Coordinator class is returned in makeCoordinator to enable event interaction via DataScannerViewControllerDelegate.
③ : Called when a QR code is recognized. Since the recognized area is set in the bounds of barcode: RecognizedItem.Barcode, a UITextView is created with the scanned string in that area and added as a subview to the dataScannerViewController's overlayContainerView.
④ : Called when the QR code recognition stops. Since it is assumed that only one item will be recognized at a time, the UITextView created in ③ is removed.
👇 How it looks running on an actual device

Reference URLs
Discussion