Enhance CameraPreviewView and ScannerView with improved session management and UI updates; add pause and resume functionality for camera, prevent duplicate detection processing, and ensure proper handling of scanning state transitions for better user experience.

main
v504 1 year ago
parent d40cb9eb99
commit e526f6cbce

@ -8,11 +8,13 @@ struct CameraPreviewView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView { func makeUIView(context: Context) -> UIView {
let view = UIView() let view = UIView()
view.backgroundColor = .black
let previewLayer = AVCaptureVideoPreviewLayer(session: session) let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.frame = view.bounds
previewLayer.videoGravity = .resizeAspectFill previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer) view.layer.addSublayer(previewLayer)
// 设置预览层为绑定变量
DispatchQueue.main.async { DispatchQueue.main.async {
self.previewLayer = previewLayer self.previewLayer = previewLayer
} }
@ -21,12 +23,12 @@ struct CameraPreviewView: UIViewRepresentable {
} }
func updateUIView(_ uiView: UIView, context: Context) { func updateUIView(_ uiView: UIView, context: Context) {
if let previewLayer = uiView.layer.sublayers?.first as? AVCaptureVideoPreviewLayer { guard let previewLayer = uiView.layer.sublayers?.first as? AVCaptureVideoPreviewLayer else { return }
// 确保预览层尺寸正确
DispatchQueue.main.async {
previewLayer.frame = uiView.bounds previewLayer.frame = uiView.bounds
self.previewLayer = previewLayer
DispatchQueue.main.async {
self.previewLayer = previewLayer
}
} }
} }
} }

@ -119,12 +119,16 @@ struct CodePositionMarker: View {
return CGPoint(x: screenSize.width / 2, y: screenSize.height / 2) return CGPoint(x: screenSize.width / 2, y: screenSize.height / 2)
} }
guard previewLayer.session?.isRunning == true else { // 即使会话停止,我们仍然可以使用预览层进行坐标转换
logWarning("Preview layer session not running, using screen center", className: "CodePositionMarker") // 因为预览层的配置仍然有效
let metadataObject = code.bounds
// 检查边界值是否有效
guard metadataObject.width > 0 && metadataObject.height > 0 else {
logWarning("Invalid metadata bounds: \(metadataObject), using screen center", className: "CodePositionMarker")
return CGPoint(x: screenSize.width / 2, y: screenSize.height / 2) return CGPoint(x: screenSize.width / 2, y: screenSize.height / 2)
} }
let metadataObject = code.bounds
let convertedPoint = previewLayer.layerPointConverted(fromCaptureDevicePoint: CGPoint( let convertedPoint = previewLayer.layerPointConverted(fromCaptureDevicePoint: CGPoint(
x: metadataObject.midX, x: metadataObject.midX,
y: metadataObject.midY y: metadataObject.midY
@ -135,6 +139,7 @@ struct CodePositionMarker: View {
return CGPoint(x: screenSize.width / 2, y: screenSize.height / 2) return CGPoint(x: screenSize.width / 2, y: screenSize.height / 2)
} }
// 确保选择点在屏幕范围内
let clampedX = max(20, min(screenSize.width - 20, convertedPoint.x)) let clampedX = max(20, min(screenSize.width - 20, convertedPoint.x))
let clampedY = max(20, min(screenSize.height - 20, convertedPoint.y)) let clampedY = max(20, min(screenSize.height - 20, convertedPoint.y))

@ -183,12 +183,19 @@ struct ScannerView: View {
logInfo(" 选择的条码内容: \(selectedCode.content)", className: "ScannerView") logInfo(" 选择的条码内容: \(selectedCode.content)", className: "ScannerView")
logInfo(" 选择的条码位置: \(selectedCode.bounds)", className: "ScannerView") logInfo(" 选择的条码位置: \(selectedCode.bounds)", className: "ScannerView")
// 停止扫描功能,避免在详情页面继续扫描
scannerViewModel.stopScanning()
logInfo("🛑 已停止扫描功能", className: "ScannerView")
// 创建 HistoryItem 并保存到 Core Data // 创建 HistoryItem 并保存到 Core Data
let historyItem = createHistoryItem(from: selectedCode) let historyItem = createHistoryItem(from: selectedCode)
// 设置选中的历史记录项并导航到详情页 // 延迟一小段时间确保扫描完全停止后再跳转
selectedHistoryItem = historyItem DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
navigateToDetail = true // 设置选中的历史记录项并导航到详情页
self.selectedHistoryItem = historyItem
self.navigateToDetail = true
}
// 发送通知(保持向后兼容) // 发送通知(保持向后兼容)
let formattedResult = "类型: \(selectedCode.type)\n内容: \(selectedCode.content)" let formattedResult = "类型: \(selectedCode.type)\n内容: \(selectedCode.content)"
@ -226,6 +233,8 @@ struct ScannerView: View {
private func pauseForPreview() { private func pauseForPreview() {
showPreviewPause = true showPreviewPause = true
// 暂停相机功能,防止在预览暂停时继续拍照
scannerViewModel.pauseCamera()
} }
private func resetToScanning() { private func resetToScanning() {
@ -234,15 +243,11 @@ struct ScannerView: View {
// 重置UI状态 // 重置UI状态
showPreviewPause = false showPreviewPause = false
// 重置扫描状态并重新开始 // 重置扫描状态
scannerViewModel.resetDetection() scannerViewModel.resetDetection()
scannerViewModel.restartScanning()
// 延迟检查会话状态 // 恢复相机功能并重新开始扫描
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { scannerViewModel.resumeCamera()
logInfo("🔍 检查扫描会话状态", className: "ScannerView")
self.scannerViewModel.checkSessionStatus()
}
logInfo("✅ ScannerView 已重置到扫描状态", className: "ScannerView") logInfo("✅ ScannerView 已重置到扫描状态", className: "ScannerView")
} }

@ -14,6 +14,7 @@ class ScannerViewModel: NSObject, ObservableObject, AVCaptureMetadataOutputObjec
var captureSession: AVCaptureSession! var captureSession: AVCaptureSession!
private var metadataOutput: AVCaptureMetadataOutput? private var metadataOutput: AVCaptureMetadataOutput?
private var videoDevice: AVCaptureDevice? private var videoDevice: AVCaptureDevice?
private var isProcessingDetection = false // 添加处理状态标记
override init() { override init() {
super.init() super.init()
@ -155,24 +156,80 @@ class ScannerViewModel: NSObject, ObservableObject, AVCaptureMetadataOutputObjec
func stopScanning() { func stopScanning() {
logInfo("🔄 停止扫描", className: "ScannerViewModel") logInfo("🔄 停止扫描", className: "ScannerViewModel")
// 立即设置处理标记,防止新的检测被处理
isProcessingDetection = true
// 检查会话是否在运行 // 检查会话是否在运行
if captureSession?.isRunning == true { if captureSession?.isRunning == true {
// 立即停止扫描会话
captureSession?.stopRunning()
logInfo("✅ 扫描会话已停止", className: "ScannerViewModel")
} else {
logInfo("ℹ️ 扫描会话已经停止", className: "ScannerViewModel")
}
}
/// 暂停相机功能(用于预览暂停状态)
func pauseCamera() {
logInfo("⏸️ 暂停相机功能", className: "ScannerViewModel")
// 设置处理标记
isProcessingDetection = true
// 停止会话(但保留配置)
if captureSession?.isRunning == true {
captureSession?.stopRunning()
logInfo("✅ 相机会话已暂停", className: "ScannerViewModel")
} else {
logInfo("ℹ️ 相机会话已经停止", className: "ScannerViewModel")
}
}
/// 恢复相机功能(用于恢复扫描)
func resumeCamera() {
logInfo("▶️ 恢复相机功能", className: "ScannerViewModel")
// 检查相机权限
guard cameraAuthorizationStatus == .authorized else {
logWarning("❌ 相机权限未授权,无法恢复相机", className: "ScannerViewModel")
return
}
// 检查会话配置
if captureSession == nil || captureSession.inputs.isEmpty || captureSession.outputs.isEmpty {
logInfo("🔄 重新设置相机会话", className: "ScannerViewModel")
setupCaptureSession()
}
// 重置处理标记
isProcessingDetection = false
// 启动相机会话
if captureSession?.isRunning != true {
logInfo("🚀 启动相机会话", className: "ScannerViewModel")
DispatchQueue.global(qos: .userInitiated).async { [weak self] in DispatchQueue.global(qos: .userInitiated).async { [weak self] in
self?.captureSession?.stopRunning() self?.captureSession?.startRunning()
DispatchQueue.main.async { DispatchQueue.main.async {
logInfo("✅ 扫描会话已停止", className: "ScannerViewModel") if self?.captureSession?.isRunning == true {
logInfo("✅ 相机会话启动成功", className: "ScannerViewModel")
} else {
logWarning("⚠️ 相机会话启动失败", className: "ScannerViewModel")
}
} }
} }
} else {
logInfo("ℹ️ 扫描会话已经停止", className: "ScannerViewModel")
} }
logInfo("✅ 相机功能已恢复", className: "ScannerViewModel")
} }
func resetDetection() { func resetDetection() {
DispatchQueue.main.async { DispatchQueue.main.async {
logInfo("🔄 重置检测状态,清空 detectedCodes", className: "ScannerViewModel") logInfo("🔄 重置检测状态,清空 detectedCodes", className: "ScannerViewModel")
self.detectedCodes = [] self.detectedCodes = []
self.isProcessingDetection = false // 重置处理标记
} }
} }
@ -193,46 +250,29 @@ class ScannerViewModel: NSObject, ObservableObject, AVCaptureMetadataOutputObjec
func restartScanning() { func restartScanning() {
logInfo("🔄 重新开始扫描", className: "ScannerViewModel") logInfo("🔄 重新开始扫描", className: "ScannerViewModel")
// 确保在主线程执行UI相关操作 // 先停止当前会话
DispatchQueue.main.async { [weak self] in if captureSession?.isRunning == true {
guard let self = self else { return } logInfo("🔄 停止当前运行的扫描会话", className: "ScannerViewModel")
captureSession?.stopRunning()
// 先停止当前会话 }
if self.captureSession?.isRunning == true {
logInfo("🔄 停止当前运行的扫描会话", className: "ScannerViewModel") // 重置检测状态
self.captureSession?.stopRunning() resetDetection()
}
// 延迟后重新启动会话
// 重置检测状态 DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.detectedCodes = [] logInfo("🔄 准备重新启动扫描会话", className: "ScannerViewModel")
// 延迟后重新启动会话 // 在后台线程启动会话
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { DispatchQueue.global(qos: .userInitiated).async { [weak self] in
self?.captureSession?.startRunning()
logInfo("🔄 准备重新启动扫描会话", className: "ScannerViewModel")
// 在后台线程启动会话 // 检查会话状态
DispatchQueue.global(qos: .userInitiated).async { DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.captureSession?.startRunning() if self?.captureSession?.isRunning == true {
logInfo("✅ 扫描会话已成功重新启动", className: "ScannerViewModel")
// 检查会话状态 } else {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { logWarning("⚠️ 扫描会话启动失败", className: "ScannerViewModel")
if self.captureSession?.isRunning == true {
logInfo("✅ 扫描会话已成功重新启动", className: "ScannerViewModel")
} else {
logWarning("⚠️ 扫描会话启动失败,尝试重新启动", className: "ScannerViewModel")
// 如果启动失败,再次尝试
DispatchQueue.global(qos: .userInitiated).async {
self.captureSession?.startRunning()
DispatchQueue.main.async {
if self.captureSession?.isRunning == true {
logInfo("✅ 扫描会话第二次尝试启动成功", className: "ScannerViewModel")
} else {
logError("❌ 扫描会话启动失败", className: "ScannerViewModel")
}
}
}
}
} }
} }
} }
@ -245,8 +285,17 @@ class ScannerViewModel: NSObject, ObservableObject, AVCaptureMetadataOutputObjec
didOutput metadataObjects: [AVMetadataObject], didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) { from connection: AVCaptureConnection) {
// 防止重复处理检测结果
guard !isProcessingDetection else {
logInfo("⚠️ 正在处理检测结果,忽略新的检测", className: "ScannerViewModel")
return
}
logInfo("metadataOutput 被调用,检测到 \(metadataObjects.count) 个对象", className: "ScannerViewModel") logInfo("metadataOutput 被调用,检测到 \(metadataObjects.count) 个对象", className: "ScannerViewModel")
// 设置处理标记
isProcessingDetection = true
// 震动反馈 // 震动反馈
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))

Loading…
Cancel
Save