import SwiftUI import CoreImage import CoreImage.CIFilterBuiltins // MARK: - QR 码生成器(macOS + iOS 通用) /// 把任意文本编码成 QR 码图片。 public enum QRCodeGenerator { private static let context = CIContext() /// 生成 QR 码位图。返回 nil 表示编码失败。 public static func image(from string: String, size: CGFloat = 260) -> PlatformImage? { guard !string.isEmpty else { return nil } let data = Data(string.utf8) let filter = CIFilter.qrCodeGenerator() filter.message = data // 容错等级 H:即使二维码中间叠 logo 也能扫出,同时更耐屏幕反光/压缩 filter.correctionLevel = "H" guard let output = filter.outputImage else { return nil } // 原始 QR 码每模块 1px,放大到目标尺寸 let scale = size / output.extent.width let scaled = output.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) #if os(macOS) guard let cg = context.createCGImage(scaled, from: scaled.extent) else { return nil } return NSImage(cgImage: cg, size: NSSize(width: size, height: size)) #else guard let cg = context.createCGImage(scaled, from: scaled.extent) else { return nil } return UIImage(cgImage: cg) #endif } } #if os(macOS) public typealias PlatformImage = NSImage #else public typealias PlatformImage = UIImage #endif // MARK: - 二维码弹窗内容视图(macOS 窗口 / iOS sheet 共用) public struct QRDisplayView: View { let urlString: String // 实际编码进二维码的内容(可能是 JSON 载荷) let displayText: String? // 界面上给用户看的明文(通常是纯 URL) let onDismiss: () -> Void @State private var copied = false public init(urlString: String, displayText: String? = nil, onDismiss: @escaping () -> Void) { self.urlString = urlString self.displayText = displayText self.onDismiss = onDismiss } /// 界面上展示的文本:优先用明文 URL,没有则展示编码内容 private var shownText: String { displayText ?? urlString } public var body: some View { VStack(spacing: 18) { Text(L.showQRCode) .font(.headline) Group { if let img = QRCodeGenerator.image(from: urlString, size: 260) { #if os(macOS) Image(nsImage: img) .interpolation(.none) .resizable() .frame(width: 260, height: 260) .padding(10) .background(Color.white) .cornerRadius(8) #else Image(uiImage: img) .interpolation(.none) .resizable() .frame(width: 260, height: 260) .padding(10) .background(Color.white) .cornerRadius(8) #endif } else { Text(L.qrGenFailed) .foregroundColor(.red) .frame(width: 260, height: 260) } } // URL 明文:方便长按/复制/手动核对 Text(shownText) .font(.system(size: 12, design: .monospaced)) .multilineTextAlignment(.center) .lineLimit(4) .truncationMode(.middle) .textSelection(.enabled) .frame(maxWidth: 300) HStack(spacing: 12) { Button(copied ? L.copied : L.copyURL) { copyToClipboard(shownText) copied = true DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { copied = false } } Button(L.done, action: onDismiss) .keyboardShortcut(.defaultAction) } } .padding(24) .frame(minWidth: 340) } private func copyToClipboard(_ s: String) { #if os(macOS) let pb = NSPasteboard.general pb.clearContents() pb.setString(s, forType: .string) #else UIPasteboard.general.string = s #endif } } #if os(macOS) /// macOS:独立窗口控制器,复用与扫码窗口一致的 NSWindow 模式 public final class QRDisplayWindowController { private var window: NSWindow? private let onClose: () -> Void public init(onClose: @escaping () -> Void = {}) { self.onClose = onClose } public func show(urlString: String, displayText: String? = nil) { if let win = window, win.isVisible { win.makeKeyAndOrderFront(nil) return } let win = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 380, height: 460), styleMask: [.titled, .closable], backing: .buffered, defer: false ) win.title = L.showQRCode win.isReleasedWhenClosed = false win.center() win.contentView = NSHostingView(rootView: QRDisplayView(urlString: urlString, displayText: displayText) { [weak self] in self?.close() }) win.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) self.window = win } public func close() { window?.close() window = nil onClose() } } #endif