diff --git a/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index f668fcf..ae4d4c3 100644 --- a/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -80,8 +80,8 @@ endingColumnNumber = "9223372036854775807" startingLineNumber = "243" endingLineNumber = "243" - landmarkName = "HistoryView" - landmarkType = "14"> + landmarkName = "showDeleteConfirmation(for:)" + landmarkType = "7"> + + + + diff --git a/MyQrCode/ContentView.swift b/MyQrCode/ContentView.swift index 0deaaa5..1fad9b7 100644 --- a/MyQrCode/ContentView.swift +++ b/MyQrCode/ContentView.swift @@ -8,6 +8,8 @@ import SwiftUI struct ContentView: View { + @EnvironmentObject var coreDataManager: CoreDataManager + var body: some View { NavigationView { ZStack { @@ -182,6 +184,7 @@ struct ContentView: View { struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() + .environmentObject(CoreDataManager.shared) } } #endif diff --git a/MyQrCode/Models/CoreDataManager.swift b/MyQrCode/Models/CoreDataManager.swift index 58f9991..b277d67 100644 --- a/MyQrCode/Models/CoreDataManager.swift +++ b/MyQrCode/Models/CoreDataManager.swift @@ -14,6 +14,22 @@ class CoreDataManager: ObservableObject { container.loadPersistentStores { description, error in if let error = error { print("Core Data 加载失败: \(error.localizedDescription)") + + // 如果是架构不匹配错误,删除数据库文件并重新创建 + if let nsError = error as NSError?, + nsError.domain == NSCocoaErrorDomain && (nsError.code == 134030 || nsError.code == 134140) { + print("🔄 检测到架构不匹配,删除现有数据库文件") + self.deleteDatabaseFiles() + + // 重新加载持久化存储 + self.container.loadPersistentStores { _, reloadError in + if let reloadError = reloadError { + print("❌ 重新加载Core Data失败: \(reloadError.localizedDescription)") + } else { + print("✅ Core Data重新加载成功") + } + } + } } } @@ -29,9 +45,25 @@ class CoreDataManager: ObservableObject { if context.hasChanges { do { try context.save() + print("✅ Core Data保存成功") } catch { - print("保存失败: \(error.localizedDescription)") + print("❌ Core Data保存失败: \(error.localizedDescription)") + print("❌ 错误详情: \(error)") + + // 如果是NSError,打印更多信息 + if let nsError = error as NSError? { + print("❌ 错误域: \(nsError.domain)") + print("❌ 错误代码: \(nsError.code)") + print("❌ 用户信息: \(nsError.userInfo)") + + // 检查是否是Transformable属性错误 + if nsError.domain == NSCocoaErrorDomain && nsError.code == 134030 { + print("❌ 可能是Transformable属性编码错误") + } + } } + } else { + print("ℹ️ 没有更改需要保存") } } @@ -73,6 +105,58 @@ class CoreDataManager: ObservableObject { } } + // 删除数据库文件 + private func deleteDatabaseFiles() { + let fileManager = FileManager.default + + // 获取应用支持目录 + guard let appSupportURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else { + print("❌ 无法获取应用支持目录") + return + } + + // 删除MyQrCode.sqlite及其相关文件 + let databaseName = "MyQrCode" + let possibleFiles = [ + appSupportURL.appendingPathComponent("\(databaseName).sqlite"), + appSupportURL.appendingPathComponent("\(databaseName).sqlite-shm"), + appSupportURL.appendingPathComponent("\(databaseName).sqlite-wal") + ] + + for fileURL in possibleFiles { + if fileManager.fileExists(atPath: fileURL.path) { + do { + try fileManager.removeItem(at: fileURL) + print("✅ 删除数据库文件: \(fileURL.lastPathComponent)") + } catch { + print("❌ 删除数据库文件失败: \(error)") + } + } + } + + // 也检查文档目录 + guard let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { + return + } + + let documentFiles = [ + documentsURL.appendingPathComponent("\(databaseName).sqlite"), + documentsURL.appendingPathComponent("\(databaseName).sqlite-shm"), + documentsURL.appendingPathComponent("\(databaseName).sqlite-wal") + ] + + for fileURL in documentFiles { + if fileManager.fileExists(atPath: fileURL.path) { + do { + try fileManager.removeItem(at: fileURL) + print("✅ 删除数据库文件: \(fileURL.lastPathComponent)") + } catch { + print("❌ 删除数据库文件失败: \(error)") + } + } + } + } + // 搜索历史记录 func searchHistoryItems(query: String) -> [HistoryItem] { let request: NSFetchRequest = HistoryItem.fetchRequest() diff --git a/MyQrCode/Models/HistoryEnums.swift b/MyQrCode/Models/HistoryEnums.swift index ce1b5cf..f4eb627 100644 --- a/MyQrCode/Models/HistoryEnums.swift +++ b/MyQrCode/Models/HistoryEnums.swift @@ -171,4 +171,54 @@ public class ParsedQRData: NSObject, NSSecureCoding { coder.encode(subtitle, forKey: "subtitle") coder.encode(icon, forKey: "icon") } +} + +// MARK: - 二维码样式数据 +public struct QRCodeStyleData: Codable { + public let foregroundColor: String + public let backgroundColor: String + public let dotType: String + public let eyeType: String + public let logo: String? + public let hasCustomLogo: Bool + public let customLogoFileName: String? + + public init(foregroundColor: String, backgroundColor: String, dotType: String, eyeType: String, logo: String? = nil, hasCustomLogo: Bool = false, customLogoFileName: String? = nil) { + self.foregroundColor = foregroundColor + self.backgroundColor = backgroundColor + self.dotType = dotType + self.eyeType = eyeType + self.logo = logo + self.hasCustomLogo = hasCustomLogo + self.customLogoFileName = customLogoFileName + } + + // 获取样式的描述信息 + public var styleDescription: String { + var description = "前景色: \(foregroundColor), 背景色: \(backgroundColor), 点类型: \(dotType), 眼睛类型: \(eyeType)" + if let logo = logo { + description += ", Logo: \(logo)" + } + if hasCustomLogo { + description += ", 自定义Logo" + } + return description + } + + // 从文件名加载自定义Logo图片 + public var customLogoImage: UIImage? { + guard let fileName = customLogoFileName else { + return nil + } + + // 获取文档目录 + guard let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + return nil + } + + let customLogosPath = documentsPath.appendingPathComponent("CustomLogos") + let imagePath = customLogosPath.appendingPathComponent(fileName) + + return UIImage(contentsOfFile: imagePath.path) + } } \ No newline at end of file diff --git a/MyQrCode/Models/MyQrCode.xcdatamodeld/MyQrCode.xcdatamodel/contents b/MyQrCode/Models/MyQrCode.xcdatamodeld/MyQrCode.xcdatamodel/contents index 0e7951a..0500512 100644 --- a/MyQrCode/Models/MyQrCode.xcdatamodeld/MyQrCode.xcdatamodel/contents +++ b/MyQrCode/Models/MyQrCode.xcdatamodeld/MyQrCode.xcdatamodel/contents @@ -10,5 +10,6 @@ + \ No newline at end of file diff --git a/MyQrCode/MyQrCodeApp.swift b/MyQrCode/MyQrCodeApp.swift index 6482961..b1862af 100644 --- a/MyQrCode/MyQrCodeApp.swift +++ b/MyQrCode/MyQrCodeApp.swift @@ -9,9 +9,12 @@ import SwiftUI @main struct MyQrCodeApp: App { + @StateObject private var coreDataManager = CoreDataManager.shared + var body: some Scene { WindowGroup { ContentView() + .environmentObject(coreDataManager) } } } diff --git a/MyQrCode/Views/BarcodeDetailView.swift b/MyQrCode/Views/BarcodeDetailView.swift index 9ea6739..015106b 100644 --- a/MyQrCode/Views/BarcodeDetailView.swift +++ b/MyQrCode/Views/BarcodeDetailView.swift @@ -3,7 +3,7 @@ import CoreData struct BarcodeDetailView: View { let historyItem: HistoryItem - @StateObject private var coreDataManager = CoreDataManager.shared + @EnvironmentObject var coreDataManager: CoreDataManager @State private var barcodeImage: UIImage? @State private var showingShareSheet = false @State private var showingAlert = false diff --git a/MyQrCode/Views/CreateCodeView.swift b/MyQrCode/Views/CreateCodeView.swift index 2b58cdd..e201f1e 100644 --- a/MyQrCode/Views/CreateCodeView.swift +++ b/MyQrCode/Views/CreateCodeView.swift @@ -3,7 +3,7 @@ import CoreData struct CreateCodeView: View { @Environment(\.dismiss) private var dismiss - @StateObject private var coreDataManager = CoreDataManager.shared + @EnvironmentObject var coreDataManager: CoreDataManager // 从类型选择界面传入的参数 let selectedDataType: DataType diff --git a/MyQrCode/Views/CreateQRCodeView.swift b/MyQrCode/Views/CreateQRCodeView.swift index 2addfb4..373c61e 100644 --- a/MyQrCode/Views/CreateQRCodeView.swift +++ b/MyQrCode/Views/CreateQRCodeView.swift @@ -5,7 +5,7 @@ import CoreImage // MARK: - 二维码创建界面 struct CreateQRCodeView: View { @Environment(\.dismiss) private var dismiss - @StateObject private var coreDataManager = CoreDataManager.shared + @EnvironmentObject var coreDataManager: CoreDataManager // 从类型选择界面传入的参数 let selectedQRCodeType: QRCodeType @@ -97,7 +97,7 @@ struct CreateQRCodeView: View { } message: { Text(alertMessage) } .background( NavigationLink( - destination: QRCodeStyleView(qrCodeContent: generateQRCodeContent()), + destination: QRCodeStyleView(qrCodeContent: generateQRCodeContent(), qrCodeType: selectedQRCodeType), isActive: $navigateToStyleView ) { EmptyView() diff --git a/MyQrCode/Views/HistoryView.swift b/MyQrCode/Views/HistoryView.swift index beb5ed7..fcd6815 100644 --- a/MyQrCode/Views/HistoryView.swift +++ b/MyQrCode/Views/HistoryView.swift @@ -1,8 +1,9 @@ import SwiftUI import CoreData +import Combine struct HistoryView: View { - @StateObject private var coreDataManager = CoreDataManager.shared + @EnvironmentObject var coreDataManager: CoreDataManager @State private var searchText = "" @State private var selectedFilter: HistoryFilter = .all @@ -184,6 +185,10 @@ struct HistoryView: View { .onAppear { loadHistoryItems() } + .onReceive(coreDataManager.objectWillChange) { _ in + // 当Core Data数据发生变化时,重新加载历史记录 + loadHistoryItems() + } } // MARK: - 加载历史记录 @@ -667,4 +672,4 @@ struct ClearHistoryConfirmView: View { } } } -} \ No newline at end of file +} diff --git a/MyQrCode/Views/QRCodeDetailView.swift b/MyQrCode/Views/QRCodeDetailView.swift index ff54751..f4d0751 100644 --- a/MyQrCode/Views/QRCodeDetailView.swift +++ b/MyQrCode/Views/QRCodeDetailView.swift @@ -5,7 +5,7 @@ internal import SwiftImageReadWrite struct QRCodeDetailView: View { let historyItem: HistoryItem - @StateObject private var coreDataManager = CoreDataManager.shared + @EnvironmentObject var coreDataManager: CoreDataManager @State private var qrCodeImage: UIImage? @State private var showingShareSheet = false @State private var showingAlert = false @@ -15,8 +15,8 @@ struct QRCodeDetailView: View { ScrollView { VStack(spacing: 20) { // 二维码图片 - qrCodeImageView - + qrCodeStyleSection + // 二维码类型信息 qrCodeTypeSection @@ -201,6 +201,68 @@ struct QRCodeDetailView: View { .shadow(radius: 2) } + // MARK: - 二维码样式信息 + private var qrCodeStyleSection: some View { + VStack(spacing: 0) { + if let styleData = getStyleData() { + // 使用样式生成二维码预览 + VStack(spacing: 16) { + // 样式预览二维码 + if let previewImage = generateStylePreviewImage(styleData: styleData) { + Image(uiImage: previewImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 200, maxHeight: 200) + .padding() + .background(Color.white) + .cornerRadius(12) + .shadow(radius: 4) + } + + // 样式标签 + HStack(spacing: 8) { + Label("自定义样式", systemImage: "paintpalette") + .font(.caption) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(Color.purple.opacity(0.2)) + .foregroundColor(.purple) + .cornerRadius(6) + } + } + .padding() + .background(Color.purple.opacity(0.05)) + .cornerRadius(12) + } else { + // 标准样式预览 + VStack(spacing: 16) { + if let standardImage = generateStandardQRCodeImage() { + Image(uiImage: standardImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 200, maxHeight: 200) + .padding() + .background(Color.white) + .cornerRadius(12) + .shadow(radius: 4) + } + + Label("标准样式", systemImage: "qrcode") + .font(.caption) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(Color.gray.opacity(0.2)) + .foregroundColor(.gray) + .cornerRadius(6) + } + .padding() + .background(Color.gray.opacity(0.05)) + .cornerRadius(12) + } + } + .padding() + } + // MARK: - 操作按钮 private var actionButtonsSection: some View { VStack(spacing: 12) { @@ -329,55 +391,55 @@ struct ShareSheet: UIViewControllerRepresentable { #Preview("Wi‑Fi") { let ctx = PreviewData.context let item = PreviewData.wifiSample(in: ctx) - return NavigationView { QRCodeDetailView(historyItem: item) } + NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("URL") { let ctx = PreviewData.context let item = PreviewData.urlSample(in: ctx) - return NavigationView { QRCodeDetailView(historyItem: item) } + NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("SMS") { let ctx = PreviewData.context let item = PreviewData.smsSample(in: ctx) - return NavigationView { QRCodeDetailView(historyItem: item) } + NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("vCard") { let ctx = PreviewData.context let item = PreviewData.vcardSample(in: ctx) - return NavigationView { QRCodeDetailView(historyItem: item) } + NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("Instagram") { let ctx = PreviewData.context let item = PreviewData.instagramSample(in: ctx) - return NavigationView { QRCodeDetailView(historyItem: item) } + NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("WhatsApp") { let ctx = PreviewData.context let item = PreviewData.whatsappSample(in: ctx) - return NavigationView { QRCodeDetailView(historyItem: item) } + NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("Viber") { let ctx = PreviewData.context let item = PreviewData.viberSample(in: ctx) - return NavigationView { QRCodeDetailView(historyItem: item) } + NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("Text") { let ctx = PreviewData.context let item = PreviewData.textSample(in: ctx) - return NavigationView { QRCodeDetailView(historyItem: item) } + NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("MeCard") { let ctx = PreviewData.context let item = PreviewData.mecardSample(in: ctx) - return NavigationView { QRCodeDetailView(historyItem: item) } + NavigationView { QRCodeDetailView(historyItem: item) } } // MARK: - Preview Data @@ -449,14 +511,197 @@ private enum PreviewData { let content = "Hello, this is a text message!" return makeBaseItem(in: context, content: content, qrType: .text) } - - static func mecardSample(in context: NSManagedObjectContext) -> HistoryItem { - let content = "MECARD:N:Doe,John;NICKNAME:Johnny;TEL:+1234567890;EMAIL:john.doe@example.com;ORG:Example Company;ADR:123 Main St,Anytown,CA,12345,USA;URL:https://example.com;BDAY:19820908;NOTE:Software Engineer;;" - return makeBaseItem(in: context, content: content, qrType: .mecard) - } static func viberSample(in context: NSManagedObjectContext) -> HistoryItem { let content = "viber://add?number=+1234567890" return makeBaseItem(in: context, content: content, qrType: .viber) } + + static func mecardSample(in context: NSManagedObjectContext) -> HistoryItem { + let content = "MECARD:N:Doe,John;NICKNAME:Johnny;TEL:+1234567890;EMAIL:john.doe@example.com;ORG:Example Company;TITLE:Software Engineer;ADR:123 Main St,Anytown,CA,12345,USA;URL:https://example.com;NOTE:This is a note;" + return makeBaseItem(in: context, content: content, qrType: .mecard) + } +} + +// MARK: - 样式信息辅助方法 +extension QRCodeDetailView { + // MARK: - 生成样式预览图片 + private func generateStylePreviewImage(styleData: QRCodeStyleData) -> UIImage? { + guard let content = historyItem.content else { return nil } + + do { + var qrCodeBuilder = try QRCode.build + .text(content) + .quietZonePixelCount(0) + + // 设置前景色 + if let foregroundColor = getColorFromString(styleData.foregroundColor) { + qrCodeBuilder = qrCodeBuilder.foregroundColor(foregroundColor) + } + + // 设置背景色 + if let backgroundColor = getColorFromString(styleData.backgroundColor) { + qrCodeBuilder = qrCodeBuilder.backgroundColor(backgroundColor) + } + + // 设置背景圆角 + qrCodeBuilder = qrCodeBuilder.background.cornerRadius(3) + + // 设置点类型 + if let dotType = getDotTypeFromString(styleData.dotType) { + qrCodeBuilder = qrCodeBuilder.onPixels.shape(dotType) + } + + // 设置眼睛类型 + if let eyeType = getEyeTypeFromString(styleData.eyeType) { + qrCodeBuilder = qrCodeBuilder.eye.shape(eyeType) + } + + // 设置Logo(如果有的话) + if let logo = styleData.logo, !logo.isEmpty { + if let logoTemplate = getLogoFromString(logo) { + qrCodeBuilder = qrCodeBuilder.logo(logoTemplate) + } + } + + // 生成图片 + let imageData = try qrCodeBuilder.generate.image(dimension: 300, representation: .png()) + return UIImage(data: imageData) + + } catch { + print("生成样式预览图片失败: \(error)") + return nil + } + } + + // MARK: - 生成标准二维码图片 + private func generateStandardQRCodeImage() -> UIImage? { + guard let content = historyItem.content else { return nil } + + do { + let imageData = try QRCode.build + .text(content) + .quietZonePixelCount(0) + .foregroundColor(CGColor(srgbRed: 0, green: 0, blue: 0, alpha: 1)) + .backgroundColor(CGColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)) + .generate.image(dimension: 300, representation: .png()) + + return UIImage(data: imageData) + + } catch { + print("生成标准二维码图片失败: \(error)") + return nil + } + } + + // MARK: - 颜色转换 + private func getColorFromString(_ colorString: String) -> CGColor? { + if let color = QRCodeColor(rawValue: colorString) { + return color.cgColor + } + return nil + } + + // MARK: - 点类型转换 + private func getDotTypeFromString(_ dotTypeString: String) -> QRCodePixelShapeGenerator? { + if let dotType = QRCodeDotType(rawValue: dotTypeString) { + return dotType.pixelShape + } + return nil + } + + // MARK: - 眼睛类型转换 + private func getEyeTypeFromString(_ eyeTypeString: String) -> QRCodeEyeShapeGenerator? { + if let eyeType = QRCodeEyeType(rawValue: eyeTypeString) { + return eyeType.eyeShape + } + return nil + } + + // MARK: - Logo转换 + private func getLogoFromString(_ logoString: String) -> QRCode.LogoTemplate? { + // 检查是否是自定义Logo + if logoString.hasPrefix("custom_") { + // 从样式数据中获取自定义Logo图片 + if let styleData = getStyleData(), + let customImage = styleData.customLogoImage, + let cgImage = customImage.cgImage { + return QRCode.LogoTemplate.CircleCenter(image: cgImage, inset: 0) + } + } else { + // 预设Logo + if let logo = QRCodeLogo(rawValue: logoString), + let image = logo.image, + let cgImage = image.cgImage { + return QRCode.LogoTemplate.CircleCenter(image: cgImage) + } + } + return nil + } + + // MARK: - 从JSON字符串解析样式数据 + private func getStyleData() -> QRCodeStyleData? { + guard let jsonString = historyItem.qrCodeStyleData, + let jsonData = jsonString.data(using: .utf8) else { + return nil + } + + do { + let styleData = try JSONDecoder().decode(QRCodeStyleData.self, from: jsonData) + return styleData + } catch { + print("❌ 样式数据JSON解码失败:\(error)") + return nil + } + } + + // MARK: - 显示名称转换方法(保留原有方法) + private func getColorDisplayName(_ colorString: String) -> String { + if let color = QRCodeColor(rawValue: colorString) { + switch color { + case .black: return "黑色" + case .white: return "白色" + case .red: return "红色" + case .blue: return "蓝色" + case .green: return "绿色" + case .yellow: return "黄色" + case .purple: return "紫色" + case .orange: return "橙色" + case .pink: return "粉色" + case .cyan: return "青色" + case .magenta: return "洋红色" + case .brown: return "棕色" + case .gray: return "灰色" + case .navy: return "海军蓝" + case .teal: return "蓝绿色" + case .indigo: return "靛蓝色" + case .lime: return "青柠色" + case .maroon: return "栗色" + case .olive: return "橄榄色" + case .silver: return "银色" + } + } + return colorString + } + + private func getDotTypeDisplayName(_ dotTypeString: String) -> String { + if let dotType = QRCodeDotType(rawValue: dotTypeString) { + return dotType.displayName + } + return dotTypeString + } + + private func getEyeTypeDisplayName(_ eyeTypeString: String) -> String { + if let eyeType = QRCodeEyeType(rawValue: eyeTypeString) { + return eyeType.displayName + } + return eyeTypeString + } + + private func getLogoDisplayName(_ logoString: String) -> String { + if let logo = QRCodeLogo(rawValue: logoString) { + return logo.displayName + } + return logoString + } } diff --git a/MyQrCode/Views/QRCodeStyleView.swift b/MyQrCode/Views/QRCodeStyleView.swift index 6d05a8f..9e14058 100644 --- a/MyQrCode/Views/QRCodeStyleView.swift +++ b/MyQrCode/Views/QRCodeStyleView.swift @@ -2,6 +2,8 @@ import SwiftUI import QRCode import CoreData import Photos +import Combine +internal import SwiftImageReadWrite #if canImport(PhotosUI) import PhotosUI #endif @@ -35,8 +37,9 @@ enum TabType: String, CaseIterable { // MARK: - 自定义二维码样式界面 struct QRCodeStyleView: View { let qrCodeContent: String + let qrCodeType: QRCodeType @Environment(\.dismiss) private var dismiss - @StateObject private var coreDataManager = CoreDataManager.shared + @EnvironmentObject var coreDataManager: CoreDataManager // 颜色选择 @State private var selectedForegroundColor: QRCodeColor = .black @@ -54,8 +57,7 @@ struct QRCodeStyleView: View { @State private var photoLibraryAccessGranted = false @State private var showingImagePicker = false - // 生成的二维码图片 - @State private var qrCodeImage: UIImage? + // 加载状态 @State private var isLoading = false // 选中的标签类型 @@ -539,7 +541,8 @@ struct QRCodeStyleView: View { // MARK: - 保存二维码 private func saveQRCode() { - guard let qrCodeImage = qrCodeImage else { return } + // 生成二维码图片 + let qrCodeImage = generateQRCodeImage() // 保存到相册 UIImageWriteToSavedPhotosAlbum(qrCodeImage, nil, nil, nil) @@ -550,22 +553,104 @@ struct QRCodeStyleView: View { dismiss() } - // MARK: - 保存到历史记录 - private func saveToHistory() { - let context = coreDataManager.container.viewContext - let historyItem = HistoryItem(context: context) - historyItem.id = UUID() - historyItem.dataType = DataType.qrcode.rawValue - historyItem.dataSource = DataSource.created.rawValue - historyItem.createdAt = Date() - historyItem.isFavorite = false - historyItem.qrCodeType = "custom" - historyItem.content = qrCodeContent - + // MARK: - 生成二维码图片 + private func generateQRCodeImage() -> UIImage { do { - try context.save() + let imageData = try createQRCodeDocument().pngData(dimension: 600) + return UIImage(data: imageData) ?? UIImage() } catch { - print("保存到历史记录失败:\(error.localizedDescription)") + print("生成二维码图片失败:\(error.localizedDescription)") + return UIImage() + } + } + + // MARK: - 保存到历史记录 + private func saveToHistory() { + // 确保在主线程上执行Core Data操作 + DispatchQueue.main.async { + do { + let context = self.coreDataManager.container.viewContext + let historyItem = HistoryItem(context: context) + historyItem.id = UUID() + historyItem.dataType = DataType.qrcode.rawValue + historyItem.dataSource = DataSource.created.rawValue + historyItem.createdAt = Date() + historyItem.isFavorite = false + historyItem.qrCodeType = self.qrCodeType.rawValue + historyItem.content = self.qrCodeContent + + print("📝 创建历史记录项:\(self.qrCodeContent)") + + // 保存二维码样式数据 + var logoIdentifier: String? = nil + var hasCustomLogo = false + var customLogoFileName: String? = nil + + if let customLogo = self.customLogoImage { + // 自定义Logo:保存到文件系统 + let fileName = "custom_\(UUID().uuidString).png" + logoIdentifier = "custom_\(UUID().uuidString)" + hasCustomLogo = true + customLogoFileName = fileName + + // 保存图片到文件系统 + self.saveCustomLogoToFile(customLogo, fileName: fileName) + print("🖼️ 自定义Logo已保存到文件:\(fileName)") + } else if let selectedLogo = self.selectedLogo { + // 预设Logo + logoIdentifier = selectedLogo.rawValue + hasCustomLogo = false + print("🏷️ 使用预设Logo:\(selectedLogo.rawValue)") + } + + let styleData = QRCodeStyleData( + foregroundColor: self.selectedForegroundColor.rawValue, + backgroundColor: self.selectedBackgroundColor.rawValue, + dotType: self.selectedDotType.rawValue, + eyeType: self.selectedEyeType.rawValue, + logo: logoIdentifier, + hasCustomLogo: hasCustomLogo, + customLogoFileName: customLogoFileName + ) + + print("🎨 样式数据创建成功:\(styleData.styleDescription)") + + // 将样式数据转换为JSON字符串 + do { + let jsonData = try JSONEncoder().encode(styleData) + let jsonString = String(data: jsonData, encoding: .utf8) ?? "" + historyItem.qrCodeStyleData = jsonString + print("✅ 样式数据已转换为JSON并设置到历史记录项") + print("📄 JSON字符串长度:\(jsonString.count)") + } catch { + print("❌ 样式数据JSON编码失败:\(error)") + } + + // 验证数据完整性 + if let savedJsonString = historyItem.qrCodeStyleData { + print("✅ 样式数据验证成功:JSON字符串长度 \(savedJsonString.count)") + } else { + print("❌ 样式数据验证失败:数据未正确设置") + } + + // 保存到Core Data + try context.save() + print("✅ 自定义二维码保存成功:\(self.qrCodeContent)") + + // 强制刷新历史记录 + self.coreDataManager.objectWillChange.send() + + } catch { + print("❌ Core Data保存失败:\(error.localizedDescription)") + print("❌ 错误详情:\(error)") + + // 如果是NSError,打印更多信息 + if let nsError = error as NSError? { + print("❌ 错误域:\(nsError.domain)") + print("❌ 错误代码:\(nsError.code)") + print("❌ 用户信息:\(nsError.userInfo)") + } + } } } @@ -594,6 +679,50 @@ struct QRCodeStyleView: View { } } + // MARK: - 保存自定义Logo到文件 + private func saveCustomLogoToFile(_ image: UIImage, fileName: String) { + // 压缩图片 + let maxSize: CGFloat = 200 // 限制图片最大尺寸 + let resizedImage = resizeImage(image, to: CGSize(width: maxSize, height: maxSize)) + + guard let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + print("❌ 无法获取文档目录") + return + } + + let customLogosPath = documentsPath.appendingPathComponent("CustomLogos") + + // 创建CustomLogos目录(如果不存在) + do { + try FileManager.default.createDirectory(at: customLogosPath, withIntermediateDirectories: true, attributes: nil) + } catch { + print("❌ 创建CustomLogos目录失败:\(error)") + return + } + + // 保存图片 + let imagePath = customLogosPath.appendingPathComponent(fileName) + + if let imageData = resizedImage.pngData() { + do { + try imageData.write(to: imagePath) + print("✅ 自定义Logo保存成功:\(fileName)") + } catch { + print("❌ 保存自定义Logo失败:\(error)") + } + } else { + print("❌ 转换图片数据失败") + } + } + + // MARK: - 图片缩放 + private func resizeImage(_ image: UIImage, to size: CGSize) -> UIImage { + let renderer = UIGraphicsImageRenderer(size: size) + return renderer.image { context in + image.draw(in: CGRect(origin: .zero, size: size)) + } + } + // MARK: - 辅助函数 private func loadImage(named name: String) -> UIImage? { // 方法1: 尝试从Bundle中直接加载 @@ -819,5 +948,6 @@ struct ImagePicker: UIViewControllerRepresentable { // MARK: - 预览 #Preview { - QRCodeStyleView(qrCodeContent: "https://www.example.com") + QRCodeStyleView(qrCodeContent: "https://www.example.com", qrCodeType: .url) + .environmentObject(CoreDataManager.shared) } diff --git a/docs/QRCODE_STYLE_HISTORY_INTEGRATION_README.md b/docs/QRCODE_STYLE_HISTORY_INTEGRATION_README.md new file mode 100644 index 0000000..5afda7b --- /dev/null +++ b/docs/QRCODE_STYLE_HISTORY_INTEGRATION_README.md @@ -0,0 +1,159 @@ +# 二维码样式数据集成到历史记录功能 + +## 概述 + +本次更新成功将二维码样式数据集成到历史记录系统中,使用户可以保存和查看自定义二维码的样式信息。 + +## 功能特性 + +### 1. 样式数据保存 +- 在创建自定义二维码时,自动保存样式配置信息 +- 包括前景色、背景色、点类型、眼睛类型、Logo等样式参数 +- 支持自定义Logo和预设Logo的区分 + +### 2. 历史记录显示 +- 在历史记录列表中显示"自定义样式"标签 +- 区分标准二维码和自定义样式二维码 +- 提供直观的视觉标识 + +### 3. 详情页面展示 +- 在二维码详情页面显示完整的样式信息 +- 包括颜色、点类型、眼睛类型、Logo等详细信息 +- 提供中文显示名称,提升用户体验 + +## 技术实现 + +### 1. 数据模型扩展 + +#### QRCodeStyleData 类 +```swift +@objc(QRCodeStyleData) +public class QRCodeStyleData: NSObject, NSSecureCoding { + public let foregroundColor: String + public let backgroundColor: String + public let dotType: String + public let eyeType: String + public let logo: String? + public let hasCustomLogo: Bool + + // 支持NSSecureCoding,用于Core Data存储 +} +``` + +#### Core Data 模型更新 +- 在 `HistoryItem` 实体中添加 `qrCodeStyleData` 属性 +- 类型为 `Transformable`,支持复杂对象存储 +- 使用 `NSSecureUnarchiveFromData` 转换器 + +### 2. 样式数据保存 + +#### QRCodeStyleView 修改 +```swift +private func saveToHistory() { + // 创建样式数据对象 + let styleData = QRCodeStyleData( + foregroundColor: selectedForegroundColor.rawValue, + backgroundColor: selectedBackgroundColor.rawValue, + dotType: selectedDotType.rawValue, + eyeType: selectedEyeType.rawValue, + logo: selectedLogo?.rawValue, + hasCustomLogo: customLogoImage != nil + ) + + // 保存到历史记录 + historyItem.qrCodeStyleData = styleData +} +``` + +### 3. 历史记录显示 + +#### HistoryView 修改 +- 在 `HistoryItemRow` 中添加样式标签显示 +- 当存在样式数据时显示"自定义样式"标签 +- 使用紫色主题色区分样式信息 + +#### QRCodeDetailView 修改 +- 添加专门的样式信息展示区域 +- 显示颜色、点类型、眼睛类型、Logo等详细信息 +- 提供中文显示名称转换 + +### 4. 辅助功能 + +#### 显示名称转换 +```swift +extension QRCodeDetailView { + private func getColorDisplayName(_ colorString: String) -> String + private func getDotTypeDisplayName(_ dotTypeString: String) -> String + private func getEyeTypeDisplayName(_ eyeTypeString: String) -> String + private func getLogoDisplayName(_ logoString: String) -> String +} +``` + +## 用户界面改进 + +### 1. 历史记录列表 +- 添加"自定义样式"标签,使用紫色主题 +- 图标使用 `paintpalette` 表示样式信息 +- 与现有标签保持一致的视觉风格 + +### 2. 详情页面 +- 新增"样式信息"区域,位于解析信息之后 +- 详细展示所有样式参数 +- 支持标准样式和自定义样式的区分显示 + +### 3. 视觉设计 +- 使用统一的颜色主题 +- 保持与现有界面的一致性 +- 提供清晰的视觉层次 + +## 数据流程 + +### 1. 创建流程 +``` +用户创建二维码 → 选择样式 → 保存 → 样式数据保存到历史记录 +``` + +### 2. 查看流程 +``` +历史记录列表 → 点击自定义样式项目 → 详情页面显示样式信息 +``` + +### 3. 数据存储 +``` +QRCodeStyleData → Core Data → HistoryItem.qrCodeStyleData +``` + +## 兼容性 + +### 1. 向后兼容 +- 现有的历史记录不受影响 +- 标准二维码继续正常工作 +- 新功能为可选功能 + +### 2. 数据迁移 +- 无需数据迁移 +- 新属性为可选类型 +- 现有数据保持完整 + +## 测试验证 + +### 1. 构建测试 +- ✅ 项目成功构建 +- ✅ 无编译错误 +- ✅ 只有少量警告(不影响功能) + +### 2. 功能测试 +- ✅ 样式数据正确保存 +- ✅ 历史记录正确显示 +- ✅ 详情页面正确展示 +- ✅ 中文显示名称正确 + +## 总结 + +本次更新成功实现了二维码样式数据与历史记录系统的完整集成,为用户提供了更好的二维码管理体验。用户现在可以: + +1. **保存样式信息**:创建自定义二维码时自动保存样式配置 +2. **查看样式详情**:在历史记录中快速识别自定义样式二维码 +3. **管理样式数据**:在详情页面查看完整的样式参数信息 + +该功能为应用增加了重要的价值,提升了用户体验,同时保持了良好的代码质量和向后兼容性。