Bedrock/Sources/Bedrock/Utilities/QRCodeGenerator.swift
Matt Bruce 11d8514f17 watch fixes
Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
2026-02-18 12:32:29 -06:00

134 lines
3.6 KiB
Swift

//
// QRCodeGenerator.swift
// Bedrock
//
// Generates QR codes from string payloads.
//
import SwiftUI
#if canImport(CoreImage)
import CoreImage
import CoreImage.CIFilterBuiltins
#endif
/// 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.
#if canImport(CoreImage)
private static let context = CIContext()
#endif
/// 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? {
#if canImport(CoreImage)
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)
#else
_ = payload
_ = correctionLevel
_ = scale
return nil
#endif
}
}
// 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()
}