import SwiftUI import CoreData import CoreImage // MARK: - 二维码创建界面 struct CreateQRCodeView: View { @Environment(\.dismiss) private var dismiss @StateObject private var coreDataManager = CoreDataManager.shared // 从类型选择界面传入的参数 let selectedQRCodeType: QRCodeType @State private var content = "" @State private var showingAlert = false @State private var alertMessage = "" // 输入焦点,确保进入页面自动弹出键盘 @FocusState private var isContentFieldFocused: Bool var body: some View { VStack(spacing: 0) { inputAndPreviewSection } .navigationTitle(selectedQRCodeType.displayName) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("创建") { createQRCode() } .disabled(content.isEmpty) .font(.system(size: 16, weight: .semibold)) } } .alert("提示", isPresented: $showingAlert) { Button("确定") { } } message: { Text(alertMessage) } .onAppear { // 稍延迟以确保进入页面时自动聚焦 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { isContentFieldFocused = true } } .onTapGesture { // 点击外部关闭键盘 isContentFieldFocused = false } } // MARK: - UI Components private var inputAndPreviewSection: some View { ScrollView { VStack(spacing: 24) { // 输入提示 VStack(spacing: 12) { HStack { Image(systemName: "info.circle") .font(.caption) .foregroundColor(.blue) Text(getContentHint()) .font(.caption) .foregroundColor(.secondary) .lineLimit(nil) Spacer() } .padding(.horizontal, 12) .padding(.vertical, 8) .background( RoundedRectangle(cornerRadius: 8) .fill(Color.blue.opacity(0.1)) ) } .padding(.horizontal, 20) // 内容输入区域 VStack(spacing: 16) { HStack { Text("输入内容") .font(.headline) .foregroundColor(.primary) Spacer() } // 多行输入框 VStack(spacing: 8) { ZStack { // 输入框主体 TextEditor(text: $content) .frame(minHeight: 120) .padding(8) .background(Color(.systemBackground)) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(isContentFieldFocused ? Color.blue : Color(.systemGray4), lineWidth: 1) ) .focused($isContentFieldFocused) .onChange(of: content) { newValue in // 限制最大字符数为150 if newValue.count > 150 { content = String(newValue.prefix(150)) } } .toolbar { ToolbarItemGroup(placement: .keyboard) { Spacer() Button("完成") { isContentFieldFocused = false } .foregroundColor(.blue) .font(.system(size: 16, weight: .medium)) } } // 占位符文本 - 左上角对齐 if content.isEmpty && !isContentFieldFocused { VStack { HStack { Text(getPlaceholderText()) .foregroundColor(.secondary) .font(.body) Spacer() } Spacer() } .padding(16) .allowsHitTesting(false) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } } // 字符计数和限制提示 - 输入框底下 HStack { Spacer() VStack(alignment: .trailing, spacing: 4) { // 字符限制提示 if content.count >= 150 { HStack(spacing: 4) { Image(systemName: "exclamationmark.triangle") .font(.caption) .foregroundColor(.orange) Text("已达到最大字符数") .font(.caption) .foregroundColor(.orange) } } else if content.count >= 140 { HStack(spacing: 4) { Image(systemName: "info.circle") .font(.caption) .foregroundColor(.blue) Text("接近字符限制") .font(.caption) .foregroundColor(.blue) } } // 字符计数 Text("\(content.count)/150") .font(.caption) .foregroundColor(getCharacterCountColor()) } } } } .padding(.horizontal, 20) // 预览区域 if !content.isEmpty { VStack(spacing: 16) { HStack { Text("预览") .font(.headline) .foregroundColor(.primary) Spacer() Button(action: { // 可以添加分享功能 }) { Image(systemName: "square.and.arrow.up") .font(.system(size: 16)) .foregroundColor(.blue) } } VStack(spacing: 16) { // 二维码图片 if let qrCodeImage = generateQRCodeImage() { Image(uiImage: qrCodeImage) .interpolation(.none) .resizable() .scaledToFit() .frame(width: 200, height: 200) .background(Color.white) .cornerRadius(12) .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) } // 内容预览卡片 VStack(alignment: .leading, spacing: 8) { HStack { Text("内容") .font(.caption) .foregroundColor(.secondary) Spacer() Text(selectedQRCodeType.displayName) .font(.caption) .padding(.horizontal, 6) .padding(.vertical, 2) .background(Color.orange.opacity(0.1)) .foregroundColor(.orange) .cornerRadius(4) } Text(formatContentForQRCodeType()) .font(.body) .foregroundColor(.primary) .textSelection(.enabled) } .padding() .background(Color(.systemGray6)) .cornerRadius(8) } .padding() .background(Color(.systemBackground)) .cornerRadius(12) .shadow(color: .black.opacity(0.05), radius: 4, x: 0, y: 2) } .padding(.horizontal, 20) } Spacer(minLength: 100) } .padding(.top, 20) } .background(Color(.systemGroupedBackground)) } // MARK: - Helper Methods private func getCharacterCountColor() -> Color { if content.count >= 150 { return .orange } else if content.count >= 140 { return .blue } else { return .secondary } } private func getContentHint() -> String { switch selectedQRCodeType { case .text: return "输入任意文本内容" case .url: return "输入网址,如:https://www.example.com" case .mail: return "输入邮箱地址,如:user@example.com" case .phone: return "输入电话号码,如:+86 138 0013 8000" case .sms: return "输入短信内容,如:Hello World" case .wifi: return "输入WiFi信息,如:SSID:MyWiFi,Password:12345678" case .vcard: return "输入联系人信息" case .mecard: return "输入联系人信息(简化版)" case .location: return "输入地理位置,如:40.7128,-74.0060" case .calendar: return "输入日历事件信息" case .instagram: return "输入Instagram用户名或链接" case .facebook: return "输入Facebook用户名或链接" case .spotify: return "输入Spotify歌曲或播放列表链接" case .twitter: return "输入Twitter用户名或链接" case .whatsapp: return "输入WhatsApp消息内容" case .viber: return "输入Viber消息内容" case .snapchat: return "输入Snapchat用户名" case .tiktok: return "输入TikTok用户名或链接" } } private func getPlaceholderText() -> String { switch selectedQRCodeType { case .text: return "输入文本内容" case .url: return "输入网址" case .mail: return "输入邮箱地址" case .phone: return "输入电话号码" case .sms: return "输入短信内容" case .wifi: return "输入WiFi信息" case .vcard: return "输入联系人信息" case .mecard: return "输入联系人信息" case .location: return "输入地理位置坐标" case .calendar: return "输入日历事件信息" case .instagram: return "输入Instagram信息" case .facebook: return "输入Facebook信息" case .spotify: return "输入Spotify链接" case .twitter: return "输入Twitter信息" case .whatsapp: return "输入WhatsApp消息" case .viber: return "输入Viber消息" case .snapchat: return "输入Snapchat用户名" case .tiktok: return "输入TikTok信息" } } private func generateQRCodeImage() -> UIImage? { guard !content.isEmpty else { return nil } // 根据二维码类型格式化内容 let formattedContent = formatContentForQRCodeType() // 生成二维码 let data = formattedContent.data(using: .utf8) let qrFilter = CIFilter.qrCodeGenerator() qrFilter.setValue(data, forKey: "inputMessage") qrFilter.setValue("H", forKey: "inputCorrectionLevel") // 高纠错级别 guard let outputImage = qrFilter.outputImage else { return nil } // 转换为UIImage let context = CIContext() guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return nil } return UIImage(cgImage: cgImage) } private func formatContentForQRCodeType() -> String { switch selectedQRCodeType { case .text: return content case .url: return content.hasPrefix("http") ? content : "https://\(content)" case .mail: return "mailto:\(content)" case .phone: return "tel:\(content)" case .sms: return "sms:\(content)" case .wifi: return "WIFI:T:WPA;S:\(content);P:password;;" case .vcard: return "BEGIN:VCARD\nVERSION:3.0\nFN:\(content)\nEND:VCARD" case .mecard: return "MECARD:N:\(content);;" case .location: return "geo:\(content)" case .calendar: return "BEGIN:VEVENT\nSUMMARY:\(content)\nEND:VEVENT" case .instagram: return "https://instagram.com/\(content)" case .facebook: return "https://facebook.com/\(content)" case .spotify: return content.hasPrefix("http") ? content : "https://open.spotify.com/track/\(content)" case .twitter: return "https://twitter.com/\(content)" case .whatsapp: return "https://wa.me/\(content)" case .viber: return "viber://chat?number=\(content)" case .snapchat: return "https://snapchat.com/add/\(content)" case .tiktok: return "https://tiktok.com/@\(content)" } } private func createQRCode() { guard !content.isEmpty else { return } let context = coreDataManager.container.viewContext let historyItem = HistoryItem(context: context) historyItem.id = UUID() historyItem.content = content historyItem.dataType = DataType.qrcode.rawValue historyItem.dataSource = DataSource.created.rawValue historyItem.createdAt = Date() historyItem.isFavorite = false historyItem.qrCodeType = selectedQRCodeType.rawValue do { try context.save() alertMessage = "二维码创建成功!" showingAlert = true // 创建成功后返回 DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { dismiss() } } catch { alertMessage = "保存失败:\(error.localizedDescription)" showingAlert = true } } } #Preview { NavigationView { CreateQRCodeView(selectedQRCodeType: .text) } }