Add QRCodeGenerator utility and make iOS-only

This commit is contained in:
Matt Bruce 2026-01-08 18:14:37 -06:00
parent 4ac3928646
commit f1a065509b

View File

@ -0,0 +1,122 @@
//
// QRCodeGenerator.swift
// Bedrock
//
// Generates QR codes from string payloads.
//
import SwiftUI
import CoreImage
import CoreImage.CIFilterBuiltins
/// Generates QR codes from string data.
///
/// Use this to create QR codes for URLs, vCards, or any text payload.
///
/// ## Example
///
/// ```swift
/// if let qrImage = QRCodeGenerator.generate(from: "https://example.com") {
/// Image(decorative: qrImage, scale: 1.0)
/// }
/// ```
public enum QRCodeGenerator {
/// The CIContext used for rendering.
private static let context = CIContext()
/// Error correction levels for QR codes.
public enum CorrectionLevel: String, Sendable {
/// Low (7% recovery)
case low = "L"
/// Medium (15% recovery)
case medium = "M"
/// Quartile (25% recovery)
case quartile = "Q"
/// High (30% recovery)
case high = "H"
}
/// Generates a QR code image from a string payload.
/// - Parameters:
/// - payload: The string to encode.
/// - correctionLevel: Error correction level (default: medium).
/// - scale: Scale factor for the output image (default: 10).
/// - Returns: A CGImage of the QR code, or nil if generation fails.
public static func generate(
from payload: String,
correctionLevel: CorrectionLevel = .medium,
scale: CGFloat = 10
) -> CGImage? {
let data = Data(payload.utf8)
let filter = CIFilter.qrCodeGenerator()
filter.setValue(data, forKey: "inputMessage")
filter.correctionLevel = correctionLevel.rawValue
guard let outputImage = filter.outputImage else { return nil }
let scaledImage = outputImage.transformed(
by: CGAffineTransform(scaleX: scale, y: scale)
)
return context.createCGImage(scaledImage, from: scaledImage.extent)
}
}
// MARK: - SwiftUI View
/// A SwiftUI view that displays a QR code.
///
/// ## Example
///
/// ```swift
/// QRCodeImageView(payload: card.vCardPayload)
/// .frame(width: 200, height: 200)
/// ```
public struct QRCodeImageView: View {
/// The string to encode as a QR code.
public let payload: String
/// Error correction level.
public let correctionLevel: QRCodeGenerator.CorrectionLevel
/// Creates a QR code view.
/// - Parameters:
/// - payload: The string to encode.
/// - correctionLevel: Error correction level (default: medium).
public init(
payload: String,
correctionLevel: QRCodeGenerator.CorrectionLevel = .medium
) {
self.payload = payload
self.correctionLevel = correctionLevel
}
public var body: some View {
if let cgImage = QRCodeGenerator.generate(
from: payload,
correctionLevel: correctionLevel
) {
Image(decorative: cgImage, scale: 1.0)
.interpolation(.none)
.resizable()
.scaledToFit()
.accessibilityLabel("QR code")
} else {
Image(systemName: "qrcode")
.font(.largeTitle)
.foregroundStyle(.secondary)
}
}
}
#Preview {
VStack(spacing: Design.Spacing.large) {
QRCodeImageView(payload: "https://example.com")
.frame(width: 150, height: 150)
QRCodeImageView(payload: "BEGIN:VCARD\nFN:John Doe\nEND:VCARD")
.frame(width: 150, height: 150)
}
.padding()
}