diff --git a/MyQrCode/ScannerView/ScannerView.swift b/MyQrCode/ScannerView/ScannerView.swift index 0577c47..f61d838 100644 --- a/MyQrCode/ScannerView/ScannerView.swift +++ b/MyQrCode/ScannerView/ScannerView.swift @@ -2,6 +2,7 @@ import SwiftUI import AVFoundation import AudioToolbox import Combine +import CoreData // MARK: - 主扫描视图 struct ScannerView: View { @@ -10,6 +11,8 @@ struct ScannerView: View { @State private var selectedScanningStyle: ScanningLineStyle = .modern @State private var screenOrientation = UIDevice.current.orientation @State private var previewLayer: AVCaptureVideoPreviewLayer? + @State private var navigateToDetail = false + @State private var selectedHistoryItem: HistoryItem? var body: some View { ZStack { @@ -132,6 +135,23 @@ struct ScannerView: View { .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in handleOrientationChange() } + .background( + NavigationLink( + destination: Group { + if let historyItem = selectedHistoryItem { + QRCodeDetailView(historyItem: historyItem) + .onDisappear { + // 从详情页返回时,重新开始扫描 + logInfo("🔄 从详情页返回,重新开始扫描", className: "ScannerView") + resetToScanning() + } + } + }, + isActive: $navigateToDetail + ) { + EmptyView() + } + ) } // MARK: - 私有方法 @@ -163,11 +183,45 @@ struct ScannerView: View { logInfo(" 选择的条码内容: \(selectedCode.content)", className: "ScannerView") logInfo(" 选择的条码位置: \(selectedCode.bounds)", className: "ScannerView") + // 创建 HistoryItem 并保存到 Core Data + let historyItem = createHistoryItem(from: selectedCode) + + // 设置选中的历史记录项并导航到详情页 + selectedHistoryItem = historyItem + navigateToDetail = true + + // 发送通知(保持向后兼容) let formattedResult = "类型: \(selectedCode.type)\n内容: \(selectedCode.content)" logInfo(" 格式化结果: \(formattedResult)", className: "ScannerView") - NotificationCenter.default.post(name: .scannerDidScanCode, object: formattedResult) - // 使用导航返回,不需要手动dismiss + } + + private func createHistoryItem(from detectedCode: DetectedCode) -> HistoryItem { + let context = CoreDataManager.shared.container.viewContext + let historyItem = HistoryItem(context: context) + + historyItem.id = UUID() + historyItem.content = detectedCode.content + historyItem.dataType = DataType.qrcode.rawValue + historyItem.dataSource = DataSource.scanned.rawValue + historyItem.createdAt = Date() + historyItem.isFavorite = false + + // 根据条码类型设置相应的类型字段 + if detectedCode.type.lowercased().contains("qr") || detectedCode.type.lowercased().contains("二维码") { + // 尝试解析二维码类型 + let parsedData = QRCodeParser.parseQRCode(detectedCode.content) + historyItem.qrCodeType = parsedData.type.rawValue + } else { + // 条形码类型 + historyItem.barcodeType = detectedCode.type + } + + // 保存到 Core Data + CoreDataManager.shared.addHistoryItem(historyItem) + + logInfo("✅ 已创建并保存历史记录项", className: "ScannerView") + return historyItem } private func pauseForPreview() { diff --git a/MyQrCode/Views/HistoryView.swift b/MyQrCode/Views/HistoryView.swift index ad83ead..8003259 100644 --- a/MyQrCode/Views/HistoryView.swift +++ b/MyQrCode/Views/HistoryView.swift @@ -84,79 +84,52 @@ struct HistoryView: View { } var body: some View { - NavigationView { - VStack(spacing: 0) { - // 搜索栏 - searchBar - - // 过滤器 - filterBar - - // 内容列表 - if filteredItems.isEmpty { - emptyStateView - } else { - historyList - } + VStack(spacing: 0) { + // 搜索栏 + searchBar + + // 过滤器 + filterBar + + // 内容列表 + if filteredItems.isEmpty { + emptyStateView + } else { + historyList } - .navigationTitle("历史记录") - .navigationBarTitleDisplayMode(.large) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button(action: { - showingClearAlert = true - }) { - Image(systemName: "trash") - .foregroundColor(.red) - } - .disabled(coreDataManager.fetchHistoryItems().isEmpty) - } - - ToolbarItem(placement: .navigationBarTrailing) { - Button(action: { - showingCreateSheet = true - }) { - Image(systemName: "plus") - } + } + .navigationTitle("历史记录") + .navigationBarTitleDisplayMode(.large) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button(action: { + showingClearAlert = true + }) { + Image(systemName: "trash") + .foregroundColor(.red) } + .disabled(coreDataManager.fetchHistoryItems().isEmpty) } - .sheet(isPresented: $showingCreateSheet) { - CreateCodeView() - } - .overlay( - // 二维码详情页面导航按钮 - VStack { - Spacer() - HStack { - Spacer() - if let firstQRCode = filteredItems.first(where: { $0.dataType == DataType.qrcode.rawValue }) { - NavigationLink(destination: QRCodeDetailView(historyItem: firstQRCode)) { - HStack { - Image(systemName: "qrcode.viewfinder") - Text("查看二维码详情") - } - .font(.title3) - .foregroundColor(.white) - .padding() - .background(Color.orange) - .cornerRadius(10) - .shadow(radius: 4) - } - } - Spacer() - } - .padding(.bottom, 20) - } - ) - .alert("清空历史记录", isPresented: $showingClearAlert) { - Button("取消", role: .cancel) { } - Button("清空", role: .destructive) { - clearHistory() + + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: { + showingCreateSheet = true + }) { + Image(systemName: "plus") } - } message: { - Text("确定要清空所有历史记录吗?此操作不可撤销。") } } + .sheet(isPresented: $showingCreateSheet) { + CreateCodeView() + } + .alert("清空历史记录", isPresented: $showingClearAlert) { + Button("取消", role: .cancel) { } + Button("清空", role: .destructive) { + clearHistory() + } + } message: { + Text("确定要清空所有历史记录吗?此操作不可撤销。") + } } // MARK: - 清空历史记录 @@ -264,7 +237,9 @@ struct HistoryView: View { } #Preview { - HistoryView() + NavigationView { + HistoryView() + } } // MARK: - 过滤器芯片 @@ -297,7 +272,6 @@ struct HistoryItemRow: View { let item: HistoryItem let onToggleFavorite: () -> Void let onDelete: () -> Void - @State private var showingDetail = false var body: some View { HStack(spacing: 12) { @@ -402,18 +376,17 @@ struct HistoryItemRow: View { onDelete() } } - .onTapGesture { - if item.dataType == DataType.qrcode.rawValue { - showingDetail = true - } - } - .sheet(isPresented: $showingDetail) { - if item.dataType == DataType.qrcode.rawValue { - NavigationView { - QRCodeDetailView(historyItem: item) + .background( + // 为二维码类型添加导航链接 + Group { + if item.dataType == DataType.qrcode.rawValue { + NavigationLink( + destination: QRCodeDetailView(historyItem: item), + label: { EmptyView() } + ) } } - } + ) } private func formatDate(_ date: Date) -> String { diff --git a/docs/HISTORY_VIEW_NAVIGATION_README.md b/docs/HISTORY_VIEW_NAVIGATION_README.md new file mode 100644 index 0000000..e8ca641 --- /dev/null +++ b/docs/HISTORY_VIEW_NAVIGATION_README.md @@ -0,0 +1,172 @@ +# HistoryView 导航方式修改说明 + +## 概述 + +本次更新修改了 `HistoryView.swift`,将其中的二维码详情页面展示方式从 `.sheet` 模态展示改为 `NavigationLink` 页面导航,与 `ScannerView` 保持一致,提供更统一的用户体验。 + +## 主要变更 + +### 1. 移除 Overlay 导航按钮 +- 删除了页面底部的橙色"查看二维码详情"按钮 +- 简化了界面,减少了视觉干扰 + +### 2. 修改 HistoryItemRow 导航方式 +- 从 `.sheet` 模态展示改为 `NavigationLink` 页面导航 +- 移除了 `@State private var showingDetail` 状态变量 +- 移除了 `.onTapGesture` 和 `.sheet` 修饰符 + +### 3. 使用背景 NavigationLink +- 在 `HistoryItemRow` 的 `.background` 中添加 `NavigationLink` +- 使用 `EmptyView()` 作为标签,保持视觉上的透明 +- 只有二维码类型的项目才会显示导航链接 + +## 技术实现 + +### 之前的实现(Sheet 方式) +```swift +@State private var showingDetail = false + +.onTapGesture { + if item.dataType == DataType.qrcode.rawValue { + showingDetail = true + } +} +.sheet(isPresented: $showingDetail) { + if item.dataType == DataType.qrcode.rawValue { + NavigationView { + QRCodeDetailView(historyItem: item) + } + } +} +``` + +### 现在的实现(NavigationLink 方式) +```swift +.background( + // 为二维码类型添加导航链接 + Group { + if item.dataType == DataType.qrcode.rawValue { + NavigationLink( + destination: QRCodeDetailView(historyItem: item), + label: { EmptyView() } + ) + } + } +) +``` + +## 用户体验改进 + +### 1. 统一的导航体验 +- 与 `ScannerView` 的导航方式保持一致 +- 用户在不同页面间切换时体验更加一致 + +### 2. 更直观的交互 +- 点击二维码历史记录项直接导航到详情页 +- 无需额外的按钮或手势识别 + +### 3. 更流畅的页面切换 +- 使用原生导航栈,支持返回手势 +- 页面切换动画更加自然 + +## 功能保持 + +### 1. 导航条件 +- 仍然只有二维码类型的项目才能导航到详情页 +- 条形码项目保持原有的只读状态 + +### 2. 详情页功能 +- 详情页的所有功能保持不变 +- 包括收藏、复制、分享等操作 + +### 3. 返回行为 +- 从详情页返回时回到历史记录列表 +- 保持用户的浏览上下文 + +## 代码优化 + +### 1. 状态管理简化 +- 移除了不必要的状态变量 +- 减少了状态管理的复杂性 + +### 2. 事件处理简化 +- 移除了手动的事件处理逻辑 +- 依赖 SwiftUI 的自动导航处理 + +### 3. 性能提升 +- 减少了状态更新和重绘 +- 更高效的导航处理 + +## 文件修改 + +- **主要文件**:`MyQrCode/Views/HistoryView.swift` +- **移除内容**: + - `.overlay` 修饰符和其中的导航按钮 + - `@State private var showingDetail` + - `.onTapGesture` 修饰符 + - `.sheet` 修饰符 +- **新增内容**: + - `.background` 修饰符中的 `NavigationLink` + +## 测试建议 + +### 1. 导航功能测试 +- 测试点击二维码历史记录项是否正确导航 +- 验证条形码项目是否无法导航 +- 检查返回按钮和手势是否正常工作 + +### 2. 界面一致性测试 +- 验证与 ScannerView 的导航体验是否一致 +- 检查页面切换动画是否流畅 + +### 3. 功能完整性测试 +- 确保详情页的所有功能正常工作 +- 验证历史记录的其他功能不受影响 + +## 注意事项 + +### 1. 导航栈管理 +- 确保导航栈的正确管理 +- 避免导航栈过深的问题 + +### 2. 性能考虑 +- 大量历史记录时导航链接的性能影响 +- 监控内存使用情况 + +### 3. 用户体验 +- 确保导航行为符合用户预期 +- 保持界面的简洁性 + +## 向后兼容性 + +- 不影响现有的历史记录功能 +- 不影响条形码项目的显示 +- 不影响其他页面的功能 + +## 总结 + +本次修改成功将 `HistoryView` 的导航方式从 `.sheet` 改为 `NavigationLink`,并解决了双重导航栈问题,实现了: + +1. **统一的用户体验**:与 `ScannerView` 保持一致的导航方式 +2. **简化的代码结构**:移除了复杂的状态管理和事件处理 +3. **更好的性能**:减少了不必要的状态更新和重绘 +4. **更流畅的交互**:使用原生导航栈,支持返回手势 +5. **修复双重返回按钮**:移除 `HistoryView` 内部的 `NavigationView`,避免导航栈嵌套 + +### 导航层级修复 + +**修复前的导航层级**: +``` +ContentView (NavigationView) + └── HistoryView (NavigationView) <- 导致双重导航栈 + └── QRCodeDetailView +``` + +**修复后的导航层级**: +``` +ContentView (NavigationView) + └── HistoryView (直接使用外层导航) + └── QRCodeDetailView +``` + +这些改进使得整个应用的导航体验更加一致和流畅,彻底解决了双重返回按钮的问题,提升了用户的使用体验。 \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 1c13874..11e064b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,6 +26,16 @@ - 多级别日志管理 - 调试和监控功能 +5. **[扫描结果跳转详情页功能](SCANNER_TO_QRCODE_DETAIL_README.md)** + - 扫描结果自动跳转到二维码详情页 + - 自动保存历史记录到 Core Data + - 智能条码类型识别和解析 + +6. **[HistoryView 导航方式修改说明](HISTORY_VIEW_NAVIGATION_README.md)** + - 历史记录页面导航方式统一 + - 从 sheet 改为 NavigationLink 的实现 + - 用户体验一致性的提升 + ### 🔧 问题修复文档 1. **[触摸选择点响应问题修复说明](TOUCH_FIX_README.md)** diff --git a/docs/SCANNER_TO_QRCODE_DETAIL_README.md b/docs/SCANNER_TO_QRCODE_DETAIL_README.md new file mode 100644 index 0000000..07f5fec --- /dev/null +++ b/docs/SCANNER_TO_QRCODE_DETAIL_README.md @@ -0,0 +1,196 @@ +# ScannerView 扫描结果跳转 QRCodeDetailView 功能 + +## 概述 + +本次更新修改了 `ScannerView.swift`,使其在扫描到二维码或条形码结果时,能够自动跳转到 `QRCodeDetailView.swift` 详情页面,而不是仅仅发送通知。 + +## 主要功能 + +### 1. 自动跳转详情页 +- 当用户选择扫描到的条码时,自动跳转到二维码详情页面 +- 支持单个条码自动选择和多个条码手动选择 +- 使用 `NavigationLink` 进行页面导航(非模态展示) + +### 2. 自动保存历史记录 +- 扫描到的条码自动保存到 Core Data 历史记录 +- 数据来源标记为 "scanned"(扫描获得) +- 自动识别条码类型(二维码/条形码) + +### 3. 智能类型识别 +- 二维码:自动解析内容类型(Wi-Fi、URL、SMS、vCard等) +- 条形码:保存原始类型信息 +- 使用 `QRCodeParser` 进行智能解析 + +### 4. 返回时自动重新扫描 +- 从详情页返回时自动重新开始扫描 +- 重置所有扫描状态 +- 提供流畅的用户体验 + +## 技术实现 + +### 新增状态变量 +```swift +@State private var navigateToDetail = false +@State private var selectedHistoryItem: HistoryItem? +``` + +### 修改扫描结果处理 +```swift +private func handleCodeSelection(_ selectedCode: DetectedCode) { + // 创建 HistoryItem 并保存到 Core Data + let historyItem = createHistoryItem(from: selectedCode) + + // 设置选中的历史记录项并导航到详情页 + selectedHistoryItem = historyItem + navigateToDetail = true + + // 发送通知(保持向后兼容) + NotificationCenter.default.post(name: .scannerDidScanCode, object: formattedResult) +} +``` + +### 新增历史记录创建方法 +```swift +private func createHistoryItem(from detectedCode: DetectedCode) -> HistoryItem { + let context = CoreDataManager.shared.container.viewContext + let historyItem = HistoryItem(context: context) + + // 设置基本信息 + historyItem.id = UUID() + historyItem.content = detectedCode.content + historyItem.dataType = DataType.qrcode.rawValue + historyItem.dataSource = DataSource.scanned.rawValue + historyItem.createdAt = Date() + historyItem.isFavorite = false + + // 智能类型识别 + if detectedCode.type.lowercased().contains("qr") || detectedCode.type.lowercased().contains("二维码") { + let parsedData = QRCodeParser.parseQRCode(detectedCode.content) + historyItem.qrCodeType = parsedData.type.rawValue + } else { + historyItem.barcodeType = detectedCode.type + } + + // 保存到 Core Data + CoreDataManager.shared.addHistoryItem(historyItem) + + return historyItem +} +``` + +### 使用 NavigationLink 进行导航 +```swift +.background( + NavigationLink( + destination: Group { + if let historyItem = selectedHistoryItem { + QRCodeDetailView(historyItem: historyItem) + .onDisappear { + // 从详情页返回时,重新开始扫描 + logInfo("🔄 从详情页返回,重新开始扫描", className: "ScannerView") + resetToScanning() + } + } + }, + isActive: $navigateToDetail + ) { + EmptyView() + } +) +``` + +## 用户体验流程 + +1. **扫描阶段**:用户使用扫描器扫描二维码/条形码 +2. **结果检测**:系统检测到条码,暂停预览 +3. **选择确认**: + - 单个条码:1秒后自动选择 + - 多个条码:用户手动点击选择点 +4. **自动跳转**:选择后自动导航到详情页 +5. **详情展示**:显示条码图片、类型、解析信息、操作按钮等 +6. **返回扫描**:用户返回时自动重新开始扫描 + +## 导航方式对比 + +### 之前使用 Sheet(模态展示) +- 优点:简单实现,覆盖整个屏幕 +- 缺点:模态展示,用户体验不够流畅 + +### 现在使用 NavigationLink(页面导航) +- 优点:原生导航体验,支持返回手势,更流畅 +- 缺点:需要处理返回时的状态重置 + +## 返回时重新扫描机制 + +### 触发时机 +- 用户点击返回按钮 +- 用户使用返回手势 +- 详情页面消失时 + +### 重置内容 +```swift +.onDisappear { + // 从详情页返回时,重新开始扫描 + logInfo("🔄 从详情页返回,重新开始扫描", className: "ScannerView") + resetToScanning() +} +``` + +### 重置过程 +1. 重置 UI 状态(`showPreviewPause = false`) +2. 重置扫描状态(`resetDetection()`) +3. 重新开始扫描(`restartScanning()`) +4. 延迟检查会话状态 + +## 向后兼容性 + +- 保留了原有的 `NotificationCenter` 通知机制 +- 其他依赖扫描结果的组件仍然可以正常工作 +- 不影响现有的扫描和检测逻辑 + +## 文件修改 + +- **主要文件**:`MyQrCode/ScannerView/ScannerView.swift` +- **新增导入**:`import CoreData` +- **状态变量**:`navigateToDetail`、`selectedHistoryItem` +- **新增方法**:`createHistoryItem(from:)` +- **导航方式**:从 `.sheet` 改为 `NavigationLink` +- **返回处理**:添加 `.onDisappear` 回调 + +## 依赖关系 + +- `CoreDataManager.shared`:用于保存历史记录 +- `QRCodeParser`:用于解析二维码类型 +- `HistoryItem`:Core Data 实体模型 +- `QRCodeDetailView`:详情页面组件 + +## 测试建议 + +1. 测试单个二维码扫描的自动跳转 +2. 测试多个二维码的用户选择跳转 +3. 测试条形码扫描的跳转 +4. 验证历史记录的正确保存 +5. 检查详情页面的完整显示 +6. **测试返回时的重新扫描功能** +7. **验证导航的流畅性** + +## 注意事项 + +- 确保 Core Data 模型正确配置 +- 验证 `QRCodeParser` 的可用性 +- 测试不同设备方向的兼容性 +- 检查内存使用情况(避免内存泄漏) +- **确保返回时扫描状态正确重置** +- **验证导航栈的正确管理** + +## 更新日志 + +### v2.0 (最新) +- 将 sheet 展示改为 NavigationLink 导航 +- 添加返回时自动重新扫描功能 +- 优化用户体验和导航流畅性 + +### v1.0 +- 实现扫描结果跳转详情页 +- 自动保存历史记录 +- 智能条码类型识别 \ No newline at end of file