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 10 months 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