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. **管理样式数据**:在详情页面查看完整的样式参数信息
+
+该功能为应用增加了重要的价值,提升了用户体验,同时保持了良好的代码质量和向后兼容性。