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