@ -15,6 +15,8 @@ struct ScannerView: View {
var body : some View {
ZStack {
// 相 机 权 限 检 查
if scannerViewModel . cameraAuthorizationStatus = = . authorized {
// 相 机 预 览 层
CameraPreviewView ( session : scannerViewModel . captureSession , previewLayer : $ previewLayer )
. ignoresSafeArea ( )
@ -44,10 +46,25 @@ struct ScannerView: View {
onSelect : handleCodeSelection
)
}
} else {
// 权 限 未 授 权 时 的 U I
CameraPermissionView (
authorizationStatus : scannerViewModel . cameraAuthorizationStatus ,
onRequestPermission : {
scannerViewModel . refreshCameraPermission ( )
} ,
onOpenSettings : {
scannerViewModel . openSettings ( )
}
)
}
}
. onAppear {
// 只 有 在 相 机 权 限 已 授 权 时 才 启 动 扫 描
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 ( )
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 {