Enhance ScannerView with automatic navigation to QR code detail page upon scanning; implement Core Data saving for scan history and improve history item handling in HistoryView with unified navigation links, enhancing user experience and interaction.

main
v504 2 months ago
parent f408fa0c04
commit 1a1989e98b

@ -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() {

@ -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 {

@ -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
```
这些改进使得整个应用的导航体验更加一致和流畅,彻底解决了双重返回按钮的问题,提升了用户的使用体验。

@ -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)**

@ -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
- 实现扫描结果跳转详情页
- 自动保存历史记录
- 智能条码类型识别
Loading…
Cancel
Save