Add QRCodeGenerator utility and make iOS-only
This commit is contained in:
parent
4ac3928646
commit
f1a065509b
122
Sources/Bedrock/Utilities/QRCodeGenerator.swift
Normal file
122
Sources/Bedrock/Utilities/QRCodeGenerator.swift
Normal 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()
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user