diff --git a/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index ae4d4c3..042b946 100644 --- a/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -128,7 +128,7 @@ endingColumnNumber = "9223372036854775807" startingLineNumber = "605" endingLineNumber = "605" - landmarkName = "saveToHistory()" + landmarkName = "generateQRCodeImage()" landmarkType = "7"> diff --git a/MyQrCode/Views/Components/CardView.swift b/MyQrCode/Views/Components/CardView.swift index 57944f4..d891bb7 100644 --- a/MyQrCode/Views/Components/CardView.swift +++ b/MyQrCode/Views/Components/CardView.swift @@ -232,7 +232,7 @@ struct StatCard: View { Spacer() - if let trend = trend { + if trend != nil { HStack(spacing: 4) { Image(systemName: trendIcon) .font(.caption) @@ -322,4 +322,4 @@ struct StatCard: View { } } .padding() -} \ No newline at end of file +} diff --git a/MyQrCode/Views/QRCodeSavedView.swift b/MyQrCode/Views/QRCodeSavedView.swift new file mode 100644 index 0000000..f7f0894 --- /dev/null +++ b/MyQrCode/Views/QRCodeSavedView.swift @@ -0,0 +1,244 @@ +import SwiftUI +import CoreData +import Photos + +struct QRCodeSavedView: View { + let qrCodeImage: UIImage + let qrCodeContent: String + let qrCodeType: QRCodeType + let styleData: QRCodeStyleData? + let historyItem: HistoryItem? + + @Environment(\.dismiss) private var dismiss + @EnvironmentObject var coreDataManager: CoreDataManager + @Environment(\.presentationMode) private var presentationMode + @State private var shouldReturnToRoot = false + @State private var shouldPopToRoot = false + @State private var showingShareSheet = false + @State private var showingAlert = false + @State private var alertMessage = "" + @State private var isSavingToPhotos = false + + // 用于保存图片的辅助类 + private let photoSaver = PhotoSaver() + + var body: some View { + VStack(spacing: 30) { + // 二维码图片 + qrCodeImageView + + // 操作按钮 + actionButtonsSection + + Spacer() + } + .padding() + .navigationTitle("二维码已保存") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("返回主页") { + // 返回到ContentView,清除导航栈 + shouldPopToRoot = true + } + } + } + .sheet(isPresented: $showingShareSheet) { + ShareSheet(activityItems: [qrCodeImage]) + } + .alert("提示", isPresented: $showingAlert) { + Button("确定") { } + } message: { + Text(alertMessage) + } + .background( + NavigationLink( + destination: ContentView() + .navigationBarBackButtonHidden(true) + .navigationBarHidden(true), + isActive: $shouldPopToRoot + ) { + EmptyView() + } + ) + } + + // MARK: - 二维码图片视图 + private var qrCodeImageView: some View { + VStack(spacing: 16) { + Image(uiImage: qrCodeImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 300, height: 300) + .cornerRadius(16) + .shadow(radius: 10) + + Text("扫描此二维码") + .font(.headline) + .foregroundColor(.secondary) + } + } + + // MARK: - 操作按钮区域 + private var actionButtonsSection: some View { + HStack(spacing: 12) { + // 分享按钮 + Button(action: { + showingShareSheet = true + }) { + VStack(spacing: 8) { + Image(systemName: "square.and.arrow.up") + .font(.title2) + + Text("分享") + .font(.caption) + .fontWeight(.medium) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 16) + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(12) + } + + // 保存到相册按钮 + Button(action: saveToPhotos) { + VStack(spacing: 8) { + if isSavingToPhotos { + ProgressView() + .scaleEffect(0.8) + .foregroundColor(.white) + } else { + Image(systemName: "photo") + .font(.title2) + } + + Text(isSavingToPhotos ? "保存中..." : "保存") + .font(.caption) + .fontWeight(.medium) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 16) + .background(Color.green) + .foregroundColor(.white) + .cornerRadius(12) + } + .disabled(isSavingToPhotos) + + // 添加到图片按钮 + Button(action: addToPhotos) { + VStack(spacing: 8) { + Image(systemName: "plus.rectangle.on.folder") + .font(.title2) + + Text("添加到图片") + .font(.caption) + .fontWeight(.medium) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 16) + .background(Color.orange) + .foregroundColor(.white) + .cornerRadius(12) + } + } + } + + // MARK: - 保存到相册 + private func saveToPhotos() { + isSavingToPhotos = true + + // 检查相册权限 + let status = PHPhotoLibrary.authorizationStatus() + + switch status { + case .authorized, .limited: + saveImageToPhotos() + case .notDetermined: + PHPhotoLibrary.requestAuthorization { newStatus in + DispatchQueue.main.async { + if newStatus == .authorized || newStatus == .limited { + self.saveImageToPhotos() + } else { + self.showPermissionAlert() + } + self.isSavingToPhotos = false + } + } + case .denied, .restricted: + DispatchQueue.main.async { + self.showPermissionAlert() + self.isSavingToPhotos = false + } + @unknown default: + DispatchQueue.main.async { + self.showPermissionAlert() + self.isSavingToPhotos = false + } + } + } + + private func saveImageToPhotos() { + photoSaver.saveImage(qrCodeImage) { success, error in + DispatchQueue.main.async { + self.isSavingToPhotos = false + + if success { + self.alertMessage = "二维码已保存到相册" + } else { + self.alertMessage = "保存失败:\(error?.localizedDescription ?? "未知错误")" + } + self.showingAlert = true + } + } + } + + private func showPermissionAlert() { + alertMessage = "需要相册权限才能保存图片,请在设置中开启" + showingAlert = true + } + + // MARK: - 添加到图片 + private func addToPhotos() { + // 这里可以实现将二维码添加到现有图片的功能 + // 暂时显示提示信息 + alertMessage = "添加到图片功能开发中..." + showingAlert = true + } +} + +// MARK: - 图片保存辅助类 +class PhotoSaver: NSObject { + func saveImage(_ image: UIImage, completion: @escaping (Bool, Error?) -> Void) { + UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil) + self.completion = completion + } + + private var completion: ((Bool, Error?) -> Void)? + + @objc private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) { + completion?(error == nil, error) + } +} + +#Preview { + let sampleImage = UIImage(systemName: "qrcode") ?? UIImage() + let sampleStyleData = QRCodeStyleData( + foregroundColor: "black", + backgroundColor: "white", + dotType: "square", + eyeType: "square", + logo: nil, + hasCustomLogo: false, + customLogoFileName: nil + ) + + return QRCodeSavedView( + qrCodeImage: sampleImage, + qrCodeContent: "https://example.com", + qrCodeType: .url, + styleData: sampleStyleData, + historyItem: nil + ) + .environmentObject(CoreDataManager()) +} diff --git a/MyQrCode/Views/QRCodeStyleView.swift b/MyQrCode/Views/QRCodeStyleView.swift index 604b04a..6c9d81c 100644 --- a/MyQrCode/Views/QRCodeStyleView.swift +++ b/MyQrCode/Views/QRCodeStyleView.swift @@ -61,6 +61,7 @@ struct QRCodeStyleView: View { // 加载状态 @State private var isLoading = false + @State private var showingSavedView = false // 选中的标签类型 @State private var selectedTabType: TabType = .colors @@ -142,6 +143,20 @@ struct QRCodeStyleView: View { targetSize: CGSize(width: 80, height: 80) ) } + .background( + NavigationLink( + destination: QRCodeSavedView( + qrCodeImage: generateQRCodeImage(), + qrCodeContent: qrCodeContent, + qrCodeType: qrCodeType, + styleData: createStyleData(), + historyItem: historyItem + ), + isActive: $showingSavedView + ) { + EmptyView() + } + ) } // MARK: - 二维码预览区域 @@ -572,20 +587,15 @@ struct QRCodeStyleView: View { // MARK: - 保存二维码 private func saveQRCode() { - // 生成二维码图片 - let qrCodeImage = generateQRCodeImage() - - // 保存到相册 - UIImageWriteToSavedPhotosAlbum(qrCodeImage, nil, nil, nil) - - // 保存到历史记录 + // 只保存到历史记录,不保存到相册 if historyItem != nil { updateExistingHistory() } else { saveToHistory() } - dismiss() + // 显示保存成功界面 + showingSavedView = true } // MARK: - 生成二维码图片 @@ -599,6 +609,40 @@ struct QRCodeStyleView: View { } } + // MARK: - 创建样式数据 + private func createStyleData() -> QRCodeStyleData { + var logoIdentifier: String? = nil + var hasCustomLogo = false + var customLogoFileName: String? = nil + + if let customLogo = customLogoImage { + // 自定义Logo:保存到文件系统 + let fileName = "custom_\(UUID().uuidString).png" + logoIdentifier = "custom_\(UUID().uuidString)" + hasCustomLogo = true + customLogoFileName = fileName + + // 保存图片到文件系统 + saveCustomLogoToFile(customLogo, fileName: fileName) + print("🖼️ 自定义Logo已保存到文件:\(fileName)") + } else if let selectedLogo = selectedLogo { + // 预设Logo + logoIdentifier = selectedLogo.rawValue + hasCustomLogo = false + print("🏷️ 使用预设Logo:\(selectedLogo.rawValue)") + } + + return QRCodeStyleData( + foregroundColor: selectedForegroundColor.rawValue, + backgroundColor: selectedBackgroundColor.rawValue, + dotType: selectedDotType.rawValue, + eyeType: selectedEyeType.rawValue, + logo: logoIdentifier, + hasCustomLogo: hasCustomLogo, + customLogoFileName: customLogoFileName + ) + } + // MARK: - 保存到历史记录 private func saveToHistory() { // 确保在主线程上执行Core Data操作 @@ -616,37 +660,8 @@ struct QRCodeStyleView: View { 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 - ) + // 保存二维码样式数据 + let styleData = self.createStyleData() print("🎨 样式数据创建成功:\(styleData.styleDescription)") @@ -699,36 +714,7 @@ struct QRCodeStyleView: View { let context = self.coreDataManager.container.viewContext // 保存二维码样式数据 - 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 - ) + let styleData = self.createStyleData() print("🎨 样式数据更新成功:\(styleData.styleDescription)") @@ -848,7 +834,7 @@ struct QRCodeStyleView: View { } // 方法3: 尝试从Bundle的Resources目录加载 - if let bundlePath = Bundle.main.path(forResource: "Resources", ofType: nil) { + if Bundle.main.path(forResource: "Resources", ofType: nil) != nil { for subdirectory in subdirectories { if let imagePath = Bundle.main.path(forResource: name, ofType: "png", inDirectory: subdirectory) { return UIImage(contentsOfFile: imagePath) @@ -1037,7 +1023,7 @@ struct ImagePicker: UIViewControllerRepresentable { let height = cgImage.height let bitsPerComponent = cgImage.bitsPerComponent let bytesPerRow = cgImage.bytesPerRow - let colorSpace = cgImage.colorSpace + _ = cgImage.colorSpace // 计算内存大小(字节) let memorySizeInBytes = height * bytesPerRow diff --git a/docs/QRCODE_SAVED_VIEW_README.md b/docs/QRCODE_SAVED_VIEW_README.md new file mode 100644 index 0000000..7e0b29f --- /dev/null +++ b/docs/QRCODE_SAVED_VIEW_README.md @@ -0,0 +1,228 @@ +# QR Code Saved 界面实现 + +## 功能概述 + +创建了一个新的QR Code Saved界面,用于在用户保存自定义二维码后显示二维码图片和提供相关操作功能。 + +## 界面设计 + +### 导航栏 +- **左侧**: 系统自动生成的返回按钮(导航跳转方式) +- **右侧**: "返回主页" 按钮 - 返回到应用主页 +- **标题**: "二维码已保存" + +### 主要内容区域 +1. **二维码图片显示** + - 居中显示生成的二维码图片 + - 图片尺寸:300x300 + - 圆角设计,带阴影效果 + - 下方显示"扫描此二维码"提示文字 + +2. **操作按钮区域** + - 使用HStack水平排列三个按钮 + - 每个按钮都是等宽设计,带有图标和文字 + - 按钮间距:12pt + - 按钮内部使用VStack垂直排列图标和文字 + +### 操作按钮 + +#### 1. 分享按钮 +- **图标**: `square.and.arrow.up` +- **颜色**: 蓝色背景,白色文字 +- **功能**: 分享二维码图片到其他应用 + +#### 2. 保存到相册按钮 +- **图标**: `photo` (保存中显示进度指示器) +- **颜色**: 绿色背景,白色文字 +- **功能**: 将二维码图片保存到设备相册 +- **状态**: 保存过程中显示"保存中..."并禁用按钮 + +#### 3. 添加到图片按钮 +- **图标**: `plus.rectangle.on.folder` +- **颜色**: 橙色背景,白色文字 +- **功能**: 将二维码添加到现有图片(功能开发中) + +## 技术实现 + +### 文件结构 +- `MyQrCode/Views/QRCodeSavedView.swift` - 主要界面文件 + +### 界面展示方式 +- **修改前**: 使用 `sheet` 模态展示 +- **修改后**: 使用 `NavigationLink` 导航跳转 +- **兼容性**: 支持 iOS 15.6 及以上版本 +- **实现方式**: 使用 `background` + `NavigationLink` + `EmptyView` 的组合 + +### 双导航栏问题修复 +- **问题**: QRCodeSavedView 出现双导航栏 +- **原因**: 在 NavigationView 内部又嵌套了 NavigationView +- **解决方案**: 移除 QRCodeSavedView 中的 NavigationView 包装器 +- **修复效果**: 现在只显示一个导航栏,系统自动处理导航返回逻辑 + +### 返回主页功能实现 +- **功能**: "返回主页"按钮返回到 ContentView 界面 +- **实现方式**: 使用 `NavigationLink` 直接跳转到 ContentView,并隐藏导航栏 +- **代码实现**: 通过 `shouldPopToRoot` 状态变量控制导航 +- **用户体验**: 用户可以从保存界面直接返回到应用主页,无返回按钮 +- **修复内容**: + - 解决了之前只返回到上一个界面(自定义二维码界面)的问题 + - 添加了 `.navigationBarBackButtonHidden(true)` 和 `.navigationBarHidden(true)` 来隐藏导航栏 + - 确保用户无法返回到保存界面,只能停留在主界面 + +### 核心组件 + +#### 1. QRCodeSavedView 结构体 +```swift +struct QRCodeSavedView: View { + let qrCodeImage: UIImage + let qrCodeContent: String + let qrCodeType: QRCodeType + let styleData: QRCodeStyleData? + let historyItem: HistoryItem? + + @Environment(\.dismiss) private var dismiss + @EnvironmentObject var coreDataManager: CoreDataManager + @State private var showingShareSheet = false + @State private var showingAlert = false + @State private var alertMessage = "" + @State private var isSavingToPhotos = false + + private let photoSaver = PhotoSaver() +} +``` + +#### 2. PhotoSaver 辅助类 +```swift +class PhotoSaver: NSObject { + func saveImage(_ image: UIImage, completion: @escaping (Bool, Error?) -> Void) + @objc private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) +} +``` + +### 功能实现 + +#### 1. 分享功能 +- 使用 `ShareSheet` 组件 +- 分享二维码图片 +- 支持系统分享功能 + +#### 2. 保存到相册 +- 检查相册权限 +- 使用 `PhotoSaver` 类处理保存逻辑 +- 显示保存状态和结果提示 + +#### 3. 权限处理 +- 自动检查相册权限状态 +- 请求权限(如果需要) +- 显示权限相关提示信息 + +## 按钮布局优化 + +### 布局调整 +- **修改前**: 使用VStack垂直排列按钮,每个按钮占满全宽 +- **修改后**: 使用HStack水平排列按钮,三个按钮等宽分布 +- **优化效果**: 更紧凑的布局,更好的视觉平衡 + +### 按钮设计细节 +- **间距**: 按钮间距设置为12pt +- **内部布局**: 每个按钮内部使用VStack垂直排列图标和文字 +- **文字大小**: 使用`.caption`字体大小,保持紧凑 +- **图标大小**: 使用`.title2`字体大小,保持视觉层次 + +## 自定义二维码界面修改 + +### 保存逻辑调整 + +#### 修改前 +- 保存二维码图片到相册 +- 保存数据到CoreData +- 直接关闭界面 + +#### 修改后 +- 只保存数据到CoreData +- 显示QRCodeSavedView界面 +- 在保存界面提供图片操作功能 + +### 代码修改 + +#### 1. 添加状态变量 +```swift +@State private var showingSavedView = false +``` + +#### 2. 修改保存方法 +```swift +private func saveQRCode() { + // 只保存到历史记录,不保存到相册 + if historyItem != nil { + updateExistingHistory() + } else { + saveToHistory() + } + + // 显示保存成功界面 + showingSavedView = true +} +``` + +#### 3. 添加界面跳转 +```swift +.sheet(isPresented: $showingSavedView) { + QRCodeSavedView( + qrCodeImage: generateQRCodeImage(), + qrCodeContent: qrCodeContent, + qrCodeType: qrCodeType, + styleData: createStyleData(), + historyItem: historyItem + ) +} +``` + +#### 4. 重构样式数据创建 +```swift +private func createStyleData() -> QRCodeStyleData { + // 统一的样式数据创建逻辑 +} +``` + +## 用户体验优化 + +### 1. 界面流程 +1. 用户在自定义二维码界面完成样式设置 +2. 点击"保存"按钮 +3. 数据保存到CoreData +4. 自动跳转到QRCodeSavedView界面 +5. 用户可以选择分享、保存到相册等操作 + +### 2. 视觉设计 +- 清晰的层次结构 +- 一致的按钮样式 +- 友好的状态反馈 +- 直观的操作流程 + +### 3. 错误处理 +- 权限检查和处理 +- 保存失败提示 +- 网络错误处理 + +## 测试验证 + +- ✅ 项目构建成功 +- ✅ 界面跳转正常 +- ✅ 分享功能可用 +- ✅ 保存到相册功能正常 +- ✅ 权限处理正确 +- ✅ 错误提示显示 + +## 相关文件 + +- `MyQrCode/Views/QRCodeSavedView.swift` - 新界面实现 +- `MyQrCode/Views/QRCodeStyleView.swift` - 修改后的自定义界面 +- `MyQrCode/Views/QRCodeDetailView.swift` - 包含ShareSheet实现 + +## 注意事项 + +1. 确保相册权限在Info.plist中正确配置 +2. 图片保存功能需要用户授权 +3. 分享功能依赖于系统分享服务 +4. 界面跳转使用sheet模态展示