diff --git a/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
index 1a5eb99..e13d109 100644
--- a/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
+++ b/MyQrCode.xcodeproj/xcuserdata/yc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -16,8 +16,8 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "146"
endingLineNumber = "146"
- landmarkName = "ScanningOverlayView"
- landmarkType = "14">
+ landmarkName = "resetToScanning()"
+ landmarkType = "7">
+ landmarkName = "resetToScanning()"
+ landmarkType = "7">
+ landmarkName = "resetToScanning()"
+ landmarkType = "7">
+ landmarkName = "ScanningOverlayView"
+ landmarkType = "14">
diff --git a/MyQrCode/ScannerView.swift b/MyQrCode/ScannerView.swift
index d20f8d2..5e07cad 100644
--- a/MyQrCode/ScannerView.swift
+++ b/MyQrCode/ScannerView.swift
@@ -15,38 +15,55 @@ struct ScannerView: View {
var body: some View {
ZStack {
- // 相机预览层
- CameraPreviewView(session: scannerViewModel.captureSession, previewLayer: $previewLayer)
- .ignoresSafeArea()
-
- // 扫描界面覆盖层
- ScanningOverlayView(
- showPreviewPause: showPreviewPause,
- selectedStyle: $selectedScanningStyle,
- detectedCodesCount: scannerViewModel.detectedCodes.count,
- onClose: { dismiss() }
- )
-
- // 条码位置标记覆盖层
- if showPreviewPause && !scannerViewModel.detectedCodes.isEmpty {
- CodePositionOverlay(
- detectedCodes: scannerViewModel.detectedCodes,
- previewLayer: previewLayer,
- onCodeSelected: handleCodeSelection,
- onRescan: resetToScanning
+ // 相机权限检查
+ if scannerViewModel.cameraAuthorizationStatus == .authorized {
+ // 相机预览层
+ CameraPreviewView(session: scannerViewModel.captureSession, previewLayer: $previewLayer)
+ .ignoresSafeArea()
+
+ // 扫描界面覆盖层
+ ScanningOverlayView(
+ showPreviewPause: showPreviewPause,
+ selectedStyle: $selectedScanningStyle,
+ detectedCodesCount: scannerViewModel.detectedCodes.count,
+ onClose: { dismiss() }
)
- }
-
- // 测试按钮(调试用)
- if showPreviewPause && scannerViewModel.detectedCodes.count == 1 {
- TestAutoSelectButton(
- detectedCode: scannerViewModel.detectedCodes[0],
- onSelect: handleCodeSelection
+
+ // 条码位置标记覆盖层
+ if showPreviewPause && !scannerViewModel.detectedCodes.isEmpty {
+ CodePositionOverlay(
+ detectedCodes: scannerViewModel.detectedCodes,
+ previewLayer: previewLayer,
+ onCodeSelected: handleCodeSelection,
+ onRescan: resetToScanning
+ )
+ }
+
+ // 测试按钮(调试用)
+ if showPreviewPause && scannerViewModel.detectedCodes.count == 1 {
+ TestAutoSelectButton(
+ detectedCode: scannerViewModel.detectedCodes[0],
+ onSelect: handleCodeSelection
+ )
+ }
+ } else {
+ // 权限未授权时的UI
+ CameraPermissionView(
+ authorizationStatus: scannerViewModel.cameraAuthorizationStatus,
+ onRequestPermission: {
+ scannerViewModel.refreshCameraPermission()
+ },
+ onOpenSettings: {
+ scannerViewModel.openSettings()
+ }
)
}
}
.onAppear {
- scannerViewModel.startScanning()
+ // 只有在相机权限已授权时才启动扫描
+ if scannerViewModel.cameraAuthorizationStatus == .authorized {
+ scannerViewModel.startScanning()
+ }
}
.onDisappear {
scannerViewModel.stopScanning()
@@ -59,6 +76,12 @@ struct ScannerView: View {
.onReceive(scannerViewModel.$detectedCodes) { codes in
handleDetectedCodes(codes)
}
+ .onReceive(scannerViewModel.$cameraAuthorizationStatus) { status in
+ if status == .authorized {
+ logInfo("🎯 相机权限已授权,启动扫描", className: "ScannerView")
+ scannerViewModel.startScanning()
+ }
+ }
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
handleOrientationChange()
}
@@ -311,13 +334,78 @@ struct CameraPreviewView: UIViewRepresentable {
class ScannerViewModel: NSObject, ObservableObject, AVCaptureMetadataOutputObjectsDelegate {
@Published var detectedCodes: [DetectedCode] = []
@Published var showAlert = false
+ @Published var cameraAuthorizationStatus: AVAuthorizationStatus = .notDetermined
+ @Published var showPermissionAlert = false
var captureSession: AVCaptureSession!
private var metadataOutput: AVCaptureMetadataOutput?
override init() {
super.init()
- setupCaptureSession()
+ checkCameraPermission()
+ }
+
+ // MARK: - 相机权限管理
+
+ private func checkCameraPermission() {
+ logInfo("🔍 检查相机权限状态", className: "ScannerViewModel")
+
+ switch AVCaptureDevice.authorizationStatus(for: .video) {
+ case .authorized:
+ logInfo("✅ 相机权限已授权", className: "ScannerViewModel")
+ cameraAuthorizationStatus = .authorized
+ setupCaptureSession()
+
+ case .notDetermined:
+ logInfo("❓ 相机权限未确定,请求权限", className: "ScannerViewModel")
+ requestCameraPermission()
+
+ case .denied, .restricted:
+ logWarning("❌ 相机权限被拒绝或受限", className: "ScannerViewModel")
+ cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
+ showPermissionAlert = true
+
+ @unknown default:
+ logWarning("❓ 未知的相机权限状态", className: "ScannerViewModel")
+ cameraAuthorizationStatus = .notDetermined
+ }
+ }
+
+ private func requestCameraPermission() {
+ logInfo("🔐 请求相机权限", className: "ScannerViewModel")
+
+ AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
+ DispatchQueue.main.async {
+ if granted {
+ logInfo("✅ 相机权限请求成功", className: "ScannerViewModel")
+ self?.cameraAuthorizationStatus = .authorized
+ self?.setupCaptureSession()
+ } else {
+ logWarning("❌ 相机权限请求被拒绝", className: "ScannerViewModel")
+ self?.cameraAuthorizationStatus = .denied
+ self?.showPermissionAlert = true
+ }
+ }
+ }
+ }
+
+ func openSettings() {
+ logInfo("⚙️ 打开系统设置", className: "ScannerViewModel")
+
+ if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
+ UIApplication.shared.open(settingsUrl) { success in
+ if success {
+ logInfo("✅ 成功打开系统设置", className: "ScannerViewModel")
+ } else {
+ logWarning("⚠️ 打开系统设置失败", className: "ScannerViewModel")
+ }
+ }
+ }
+ }
+
+ func refreshCameraPermission() {
+ logInfo("🔍 重新检查相机权限状态", className: "ScannerViewModel")
+ checkCameraPermission()
}
// MARK: - 相机设置
@@ -882,6 +970,85 @@ extension Notification.Name {
static let scannerDidScanCode = Notification.Name("scannerDidScanCode")
}
+// MARK: - 相机权限视图
+struct CameraPermissionView: View {
+ let authorizationStatus: AVAuthorizationStatus
+ let onRequestPermission: () -> Void
+ let onOpenSettings: () -> Void
+
+ var body: some View {
+ VStack(spacing: 30) {
+ Spacer()
+
+ // 相机图标
+ Image(systemName: "camera.fill")
+ .font(.system(size: 80))
+ .foregroundColor(.gray)
+
+ // 标题
+ Text("camera_permission_title".localized)
+ .font(.largeTitle)
+ .fontWeight(.bold)
+ .multilineTextAlignment(.center)
+
+ // 描述文本
+ Text(getDescriptionText())
+ .font(.body)
+ .multilineTextAlignment(.center)
+ .foregroundColor(.secondary)
+ .padding(.horizontal, 40)
+
+ // 操作按钮
+ VStack(spacing: 15) {
+ if authorizationStatus == .notDetermined {
+ Button(action: onRequestPermission) {
+ HStack {
+ Image(systemName: "camera.badge.ellipsis")
+ Text("request_camera_permission".localized)
+ }
+ .font(.headline)
+ .foregroundColor(.white)
+ .frame(maxWidth: .infinity)
+ .padding()
+ .background(Color.blue)
+ .cornerRadius(12)
+ }
+ } else if authorizationStatus == .denied || authorizationStatus == .restricted {
+ Button(action: onOpenSettings) {
+ HStack {
+ Image(systemName: "gear")
+ Text("open_settings".localized)
+ }
+ .font(.headline)
+ .foregroundColor(.white)
+ .frame(maxWidth: .infinity)
+ .padding()
+ .background(Color.orange)
+ .cornerRadius(12)
+ }
+ }
+ }
+ .padding(.horizontal, 40)
+
+ Spacer()
+ }
+ .background(Color(.systemBackground))
+ }
+
+ private func getDescriptionText() -> String {
+ switch authorizationStatus {
+ case .notDetermined:
+ return "camera_permission_description".localized
+ case .denied:
+ return "camera_permission_denied".localized
+ case .restricted:
+ return "camera_permission_restricted".localized
+ default:
+ return "camera_permission_unknown".localized
+ }
+ }
+}
+
#if DEBUG
struct ScannerView_Previews: PreviewProvider {
static var previews: some View {
diff --git a/MyQrCode/en.lproj/Localizable.strings b/MyQrCode/en.lproj/Localizable.strings
index d7f1cfe..34b4c41 100644
--- a/MyQrCode/en.lproj/Localizable.strings
+++ b/MyQrCode/en.lproj/Localizable.strings
@@ -34,6 +34,15 @@
// Test Button
"test_auto_select" = "Test Auto Select";
+// Camera Permission
+"camera_permission_title" = "Camera Permission Required";
+"camera_permission_description" = "This app needs access to your camera to scan QR codes and barcodes. Please grant camera permission to continue.";
+"camera_permission_denied" = "Camera access has been denied. Please enable camera permission in Settings to use the scanner.";
+"camera_permission_restricted" = "Camera access is restricted. Please check your device settings or contact your administrator.";
+"camera_permission_unknown" = "Camera permission status is unknown. Please check your device settings.";
+"request_camera_permission" = "Grant Camera Access";
+"open_settings" = "Open Settings";
+
// Language Settings
"select_language" = "Select Language";
"language_changes_info" = "Language changes will take effect immediately";
diff --git a/MyQrCode/zh-Hans.lproj/Localizable.strings b/MyQrCode/zh-Hans.lproj/Localizable.strings
index 9b72c84..07f259e 100644
--- a/MyQrCode/zh-Hans.lproj/Localizable.strings
+++ b/MyQrCode/zh-Hans.lproj/Localizable.strings
@@ -34,6 +34,15 @@
// 测试按钮
"test_auto_select" = "测试自动选择";
+// 相机权限
+"camera_permission_title" = "需要相机权限";
+"camera_permission_description" = "此应用需要访问您的相机来扫描二维码和条形码。请授予相机权限以继续使用。";
+"camera_permission_denied" = "相机访问被拒绝。请在设置中启用相机权限以使用扫描器。";
+"camera_permission_restricted" = "相机访问受限。请检查您的设备设置或联系管理员。";
+"camera_permission_unknown" = "相机权限状态未知。请检查您的设备设置。";
+"request_camera_permission" = "授予相机权限";
+"open_settings" = "打开设置";
+
// 语言设置
"select_language" = "选择语言";
"language_changes_info" = "语言更改将立即生效";