From d929fd046a523a34363c3ffdb13cf836f1152ccb Mon Sep 17 00:00:00 2001 From: v504 Date: Wed, 20 Aug 2025 15:12:08 +0800 Subject: [PATCH] Refactor CodePositionOverlay and ScannerView to remove the rescan button from the overlay and integrate it into the navigation toolbar; enhance user experience by providing haptic feedback on rescan actions and improving code structure. --- .../ScannerView/CodePositionOverlay.swift | 69 +----- MyQrCode/ScannerView/ScannerView.swift | 28 ++- docs/RESCAN_BUTTON_TOOLBAR_README.md | 221 ++++++++++++++++++ 3 files changed, 254 insertions(+), 64 deletions(-) create mode 100644 docs/RESCAN_BUTTON_TOOLBAR_README.md diff --git a/MyQrCode/ScannerView/CodePositionOverlay.swift b/MyQrCode/ScannerView/CodePositionOverlay.swift index caa14ac..d4008fb 100644 --- a/MyQrCode/ScannerView/CodePositionOverlay.swift +++ b/MyQrCode/ScannerView/CodePositionOverlay.swift @@ -6,14 +6,13 @@ struct CodePositionOverlay: View { let detectedCodes: [DetectedCode] let previewLayer: AVCaptureVideoPreviewLayer? let onCodeSelected: (DetectedCode) -> Void - let onRescan: () -> Void var body: some View { - GeometryReader { geometry in - ZStack { + ZStack { + GeometryReader { geometry in ForEach(detectedCodes) { code in CodePositionMarker( - code: code, + code: code, screenSize: geometry.size, previewLayer: previewLayer, onCodeSelected: onCodeSelected @@ -21,7 +20,7 @@ struct CodePositionOverlay: View { } // 调试信息:显示触摸区域边界 - #if DEBUG +#if DEBUG ForEach(detectedCodes) { code in let position = calculateDebugPosition(code: code, screenSize: geometry.size) Rectangle() @@ -30,63 +29,9 @@ struct CodePositionOverlay: View { .position(x: position.x, y: position.y) .opacity(0.3) } - #endif - - // 重新扫描按钮 - 放在右上角 - VStack { - HStack { - Spacer() - Button(action: { - logInfo("🔄 用户点击重新扫描按钮", className: "CodePositionOverlay") - - // 添加触觉反馈 - let impactFeedback = UIImpactFeedbackGenerator(style: .medium) - impactFeedback.impactOccurred() - - onRescan() - }) { - HStack(spacing: 8) { - Image(systemName: "arrow.clockwise") - .font(.system(size: 18, weight: .semibold)) - .rotationEffect(.degrees(0)) - .animation(.easeInOut(duration: 0.3), value: true) - - Text("rescan_button".localized) - .font(.system(size: 15, weight: .semibold)) +#endif } - .foregroundColor(.white) - .padding(.horizontal, 20) - .padding(.vertical, 10) - .background( - RoundedRectangle(cornerRadius: 25) - .fill(Color.blue.opacity(0.9)) - .shadow(color: .black.opacity(0.3), radius: 4, x: 0, y: 2) - ) - } - .buttonStyle(RescanButtonStyle()) - .padding(.trailing, 25) - .padding(.top, 25) - - // 调试按钮:检查会话状态 - #if DEBUG - Button(action: { - logInfo("🔍 调试:检查扫描会话状态", className: "CodePositionOverlay") - // 这里可以添加会话状态检查逻辑 - }) { - Image(systemName: "info.circle") - .font(.system(size: 16, weight: .medium)) - .foregroundColor(.yellow) - .padding(8) - .background(Color.black.opacity(0.6)) - .clipShape(Circle()) - } - .padding(.trailing, 10) - .padding(.top, 25) - #endif - } - Spacer() - } - } + .ignoresSafeArea() } .allowsHitTesting(true) .contentShape(Rectangle()) // 确保整个区域都可以接收触摸事件 @@ -210,4 +155,4 @@ struct RescanButtonStyle: ButtonStyle { .opacity(configuration.isPressed ? 0.8 : 1.0) .animation(.easeInOut(duration: 0.1), value: configuration.isPressed) } -} \ No newline at end of file +} diff --git a/MyQrCode/ScannerView/ScannerView.swift b/MyQrCode/ScannerView/ScannerView.swift index 58fc794..f27eecc 100644 --- a/MyQrCode/ScannerView/ScannerView.swift +++ b/MyQrCode/ScannerView/ScannerView.swift @@ -31,8 +31,7 @@ struct ScannerView: View { CodePositionOverlay( detectedCodes: scannerViewModel.detectedCodes, previewLayer: previewLayer, - onCodeSelected: handleCodeSelection, - onRescan: resetToScanning + onCodeSelected: handleCodeSelection ) } @@ -59,6 +58,31 @@ struct ScannerView: View { .navigationTitle("扫描器") .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden(false) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + // 重新扫描按钮 - 只在预览暂停状态时显示 + if showPreviewPause { + Button(action: { + logInfo("🔄 用户点击工具栏重新扫描按钮", className: "ScannerView") + + // 添加触觉反馈 + let impactFeedback = UIImpactFeedbackGenerator(style: .medium) + impactFeedback.impactOccurred() + + resetToScanning() + }) { + HStack(spacing: 6) { + Image(systemName: "arrow.clockwise") + .font(.system(size: 16, weight: .semibold)) + + Text("rescan_button".localized) + .font(.system(size: 14, weight: .medium)) + } + .foregroundColor(.blue) + } + } + } + } .onAppear { // 只有在相机权限已授权时才启动扫描 if scannerViewModel.cameraAuthorizationStatus == .authorized { diff --git a/docs/RESCAN_BUTTON_TOOLBAR_README.md b/docs/RESCAN_BUTTON_TOOLBAR_README.md new file mode 100644 index 0000000..046345a --- /dev/null +++ b/docs/RESCAN_BUTTON_TOOLBAR_README.md @@ -0,0 +1,221 @@ +# 重新扫描按钮移动到工具栏说明 + +## 🎯 修改目标 + +将重新扫描按钮从 `CodePositionOverlay` 覆盖层移动到 `ScannerView` 的导航工具栏中,提供更好的用户体验和界面一致性。 + +## 🔄 主要修改内容 + +### 1. **CodePositionOverlay.swift 的修改** + +#### 移除重新扫描按钮 +- 删除了整个重新扫描按钮的 VStack 容器 +- 移除了调试按钮 +- 简化了界面结构 + +#### 更新结构体定义 +```swift +// 修改前 +struct CodePositionOverlay: View { + let detectedCodes: [DetectedCode] + let previewLayer: AVCaptureVideoPreviewLayer? + let onCodeSelected: (DetectedCode) -> Void + let onRescan: () -> Void // 移除 + +// 修改后 +struct CodePositionOverlay: View { + let detectedCodes: [DetectedCode] + let previewLayer: AVCaptureVideoPreviewLayer? + let onCodeSelected: (DetectedCode) -> Void +``` + +#### 简化界面布局 +```swift +// 修改前:复杂的按钮布局 +VStack { + HStack { + Spacer() + Button(action: { onRescan() }) { + // 复杂的按钮样式 + } + .buttonStyle(RescanButtonStyle()) + .padding(.trailing, 25) + .padding(.top, 25) + + // 调试按钮 + #if DEBUG + Button(action: { /* 调试逻辑 */ }) { + // 调试按钮样式 + } + .padding(.trailing, 10) + .padding(.top, 25) + #endif + } + Spacer() +} + +// 修改后:简洁的布局 +} +.ignoresSafeArea() +``` + +### 2. **ScannerView.swift 的修改** + +#### 添加工具栏 +```swift +.toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + // 重新扫描按钮 - 只在预览暂停状态时显示 + if showPreviewPause { + Button(action: { + logInfo("🔄 用户点击工具栏重新扫描按钮", className: "ScannerView") + + // 添加触觉反馈 + let impactFeedback = UIImpactFeedbackGenerator(style: .medium) + impactFeedback.impactOccurred() + + resetToScanning() + }) { + HStack(spacing: 6) { + Image(systemName: "arrow.clockwise") + .font(.system(size: 16, weight: .semibold)) + + Text("rescan_button".localized) + .font(.system(size: 14, weight: .medium)) + } + .foregroundColor(.blue) + } + } + } +} +``` + +#### 更新 CodePositionOverlay 调用 +```swift +// 修改前 +CodePositionOverlay( + detectedCodes: scannerViewModel.detectedCodes, + previewLayer: previewLayer, + onCodeSelected: handleCodeSelection, + onRescan: resetToScanning // 移除 +) + +// 修改后 +CodePositionOverlay( + detectedCodes: scannerViewModel.detectedCodes, + previewLayer: previewLayer, + onCodeSelected: handleCodeSelection +) +``` + +## 🚀 修改的优势 + +### 1. **用户体验改进** +- **位置一致性**: 重新扫描按钮现在位于标准的导航工具栏位置 +- **可见性提升**: 工具栏按钮更容易被用户发现和访问 +- **操作便利性**: 符合 iOS 设计规范,用户更容易理解 + +### 2. **界面布局优化** +- **简化覆盖层**: `CodePositionOverlay` 现在专注于条码位置标记 +- **减少视觉干扰**: 移除了覆盖层中的额外按钮,界面更清晰 +- **更好的层次结构**: 工具栏和内容区域分离,层次更清晰 + +### 3. **代码质量提升** +- **职责分离**: 每个组件都有明确的职责 +- **减少参数传递**: 不需要在组件间传递 `onRescan` 回调 +- **更容易维护**: 按钮逻辑集中在主视图中 + +### 4. **设计一致性** +- **符合 iOS 规范**: 工具栏是 iOS 应用的标准元素 +- **导航体验**: 与应用的导航结构保持一致 +- **视觉统一**: 按钮样式与系统工具栏保持一致 + +## 📱 界面变化对比 + +### 修改前 +- 重新扫描按钮位于 `CodePositionOverlay` 的右上角 +- 按钮样式复杂,包含阴影和渐变 +- 调试按钮与重新扫描按钮混合显示 +- 覆盖层界面较为复杂 + +### 修改后 +- 重新扫描按钮位于导航栏右侧工具栏 +- 按钮样式简洁,符合系统设计 +- 只在预览暂停状态时显示 +- 界面更加清晰和专注 + +## 🧪 测试要点 + +### 1. **功能测试** +- ✅ 重新扫描按钮在预览暂停状态时正确显示 +- ✅ 点击按钮正确触发重新扫描功能 +- ✅ 触觉反馈正常工作 +- ✅ 按钮在非预览暂停状态时隐藏 + +### 2. **界面测试** +- ✅ 按钮位置正确(导航栏右侧) +- ✅ 按钮样式符合系统设计 +- ✅ 按钮文本正确显示 +- ✅ 图标正确显示 + +### 3. **交互测试** +- ✅ 按钮响应触摸事件 +- ✅ 重新扫描功能正常工作 +- ✅ 界面状态正确更新 + +## 🔧 技术实现细节 + +### 1. **工具栏配置** +```swift +.toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + // 按钮内容 + } +} +``` + +### 2. **条件显示** +```swift +if showPreviewPause { + Button(action: { /* 操作 */ }) { + // 按钮内容 + } +} +``` + +### 3. **触觉反馈** +```swift +let impactFeedback = UIImpactFeedbackGenerator(style: .medium) +impactFeedback.impactOccurred() +``` + +### 4. **本地化支持** +```swift +Text("rescan_button".localized) + .font(.system(size: 14, weight: .medium)) +``` + +## 🚨 注意事项 + +### 1. **状态管理** +- 确保 `showPreviewPause` 状态正确管理 +- 按钮只在适当的时候显示 + +### 2. **用户反馈** +- 保持触觉反馈功能 +- 确保按钮操作有适当的视觉反馈 + +### 3. **性能考虑** +- 工具栏按钮的显示/隐藏不会影响性能 +- 条件渲染确保不必要的视图不会被创建 + +## 📊 修改总结 + +通过这次修改,我们成功地将重新扫描按钮移动到了工具栏中: + +1. **界面优化**: 简化了 `CodePositionOverlay` 的布局 +2. **用户体验**: 按钮位置更符合用户期望 +3. **代码质量**: 减少了组件间的耦合 +4. **设计一致性**: 符合 iOS 设计规范 + +现在重新扫描按钮位于导航工具栏中,提供了更好的用户体验和界面一致性。按钮只在需要时显示,界面更加清晰和专注。🎉 \ No newline at end of file