From ad8b959a58fa6b6f226601d8fd488a5d99faaae0 Mon Sep 17 00:00:00 2001 From: v504 Date: Tue, 26 Aug 2025 17:49:45 +0800 Subject: [PATCH] Integrate CoreDataManager as an environment object across multiple views, enhancing data management and state handling. Implement error handling for Core Data loading and saving processes, including database file deletion for schema mismatches. Introduce QRCodeStyleData struct for managing QR code customization styles, and update QRCodeDetailView to display custom styles effectively. Enhance QRCodeStyleView to save custom logos and styles to Core Data, improving user experience in QR code creation and management. --- .../xcdebugger/Breakpoints_v2.xcbkptlist | 20 +- MyQrCode/ContentView.swift | 3 + MyQrCode/Models/CoreDataManager.swift | 86 +++++- MyQrCode/Models/HistoryEnums.swift | 50 ++++ .../MyQrCode.xcdatamodel/contents | 1 + MyQrCode/MyQrCodeApp.swift | 3 + MyQrCode/Views/BarcodeDetailView.swift | 2 +- MyQrCode/Views/CreateCodeView.swift | 2 +- MyQrCode/Views/CreateQRCodeView.swift | 4 +- MyQrCode/Views/HistoryView.swift | 9 +- MyQrCode/Views/QRCodeDetailView.swift | 279 ++++++++++++++++-- MyQrCode/Views/QRCodeStyleView.swift | 168 +++++++++-- ...QRCODE_STYLE_HISTORY_INTEGRATION_README.md | 159 ++++++++++ 13 files changed, 741 insertions(+), 45 deletions(-) create mode 100644 docs/QRCODE_STYLE_HISTORY_INTEGRATION_README.md 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. **管理样式数据**:在详情页面查看完整的样式参数信息 + +该功能为应用增加了重要的价值,提升了用户体验,同时保持了良好的代码质量和向后兼容性。