
不知道你有没有遇到过这样的场景用 iPhone 原生相机拍夜景时画面偏暗想手动拉高快门时间却发现系统相机只能点按对焦、左右滑曝光补偿参数调节的精度远远不够想拍一段需要固定参数的长曝光原生相机又不能锁定 ISO想加一个网格线辅助构图又觉得默认的网格太粗不够灵活。这也是不少 iOS 开发者想自己写相机应用的原因。“Camera Sun”这个项目就是在“系统相机不够顺手”这个痛点上做文章。它并不是去修改 iOS 系统自带的相机 App而是提供一套完全可控的相机应用实现思路用 AVFoundation 搭建一套轻量级的相机采集流程再通过 SwiftUI 重新设计交互界面。本文就围绕这个思路拆解如何一步步在 iOS 上实现一个更符合自己习惯的相机工具。本文适合正在学习 AVFoundation 相机开发的 iOS 开发者也适合对原生相机功能不满意、想做一个私有相机应用的技术爱好者。阅读后你会掌握 iOS 相机应用的核心采集链路、权限处理方式、手动对焦与曝光补偿的基本实现以及相机应用开发中常见的黑屏、权限崩溃、预览卡顿等问题排查方法。1. 背景与核心概念1.1 iPhone 原生相机 App 到底差在哪iPhone 的原生相机 App 在计算摄影上表现非常出色比如智能 HDR、深度融合、夜间模式这些能力都依赖苹果的多帧合成算法。但原生相机 App 的设计目标是“让普通用户随手拍出好照片”因此它会故意隐藏手动控制项不支持单独调节快门速度、ISO、白平衡、对焦距离不支持在拍照前锁定测光和对焦位置AE/AF Lock网格线只有三分法和水平仪两种不够灵活不能自定义拍照按钮的位置不支持把相机实时取景画面嵌入到自己的业务 App 中。如果你是普通用户这些缺失并不影响体验但如果你是摄影师或者开发者你在某些固定场景下就会需要更精细的控制。比如拍摄流水时想固定 1/4 秒快门制造丝绢般的效果拍摄星空时需要把 ISO 控制在一个合理范围并延长曝光做二维码扫描、OCR、AR 小应用时需要把相机画面集成在自定义 UI 中想用 RAW 格式输出保留更多后期空间。这些需求使用 UIKit 或 SwiftUI 结合 AVFoundation 完全可以实现。1.2 Camera Sun 项目思路“Camera Sun”是一个概念型开源相机应用。它的目标不是去修改系统相机而是通过 AVFoundation 重新实现一套“更透明”的相机流程。所谓透明体现在两个层面采集链路透明你可以看到预览、拍摄、保存每一环数据流是什么参数控制透明曝光补偿、测光模式、对焦模式都可以手动调整。从技术架构上看Camera Sun 的核心思路是AVCaptureDevice相机设备 ↓ AVCaptureDeviceInput设备输入 ↓ AVCaptureSession采集会话 ↓ AVCaptureVideoPreviewLayer预览层 ↓ AVCapturePhotoOutput照片输出整个应用维持一个AVCaptureSession会话通过预设质量、输入输出配置完成从实时预览到照片捕获的流程。UI 层使用 SwiftUI 来构建把系统相机里的复杂功能拆成可选项。1.3 为什么开发者值得学习这个案例学习 Camera Sun 的实现不只是为了做一个相机 App。AVFoundation 的相机采集链路在很多业务场景中都会用到扫码类 App 需要自定义扫描框和相机控制远程协助类 App 需要实时共享相机画面直播类 App 需要同时输出预览流和编码流AI 视觉类 App 需要逐帧获取像素数据。掌握了AVCaptureSession的配置、输入输出管理、权限流程就等于掌握了 iOS 相机的底层开关。之后再接入 Vision、Core ML、Metal 渲染都会更容易。2. 环境准备与版本说明在开始编写代码之前先梳理一下本文示例所依赖的环境。项目说明操作系统macOS 14 或更新版本开发工具Xcode 15 以上语言Swift 5.9UI 框架SwiftUI相机框架AVFoundation最低系统版本iOS 17.0示例项目运行设备真机模拟器无法使用相机注意相机相关功能必须在真机上运行iOS Simulator 不提供真实相机硬件。如果你的项目需要兼容 iOS 16可以把最低版本调低但部分 SwiftUI API 可能有差异需要按实际版本调整。示例项目的目录结构计划如下方便读者对照着建项目CameraSun ├── CameraSunApp.swift ├── ContentView.swift ├── Camera/ │ ├── CameraService.swift │ ├── CameraPreviewView.swift │ └── CameraControlPanel.swift └── Info.plist后续的代码示例都会按这个结构展开。如果你的项目命名不同只需对应修改类名。3. 核心语法、配置或原理拆解3.1 AVCaptureSession 的采集管线AVCaptureSession是 AVFoundation 提供的核心会话对象负责把输入设备的数据桥接到输出模块。一个完整的采集链路包含三个部分输入AVCaptureDeviceInput代表摄像头、麦克风等硬件输入会话AVCaptureSession负责协调数据流输出AVCapturePhotoOutput、AVCaptureMovieFileOutput、AVCaptureVideoDataOutput等负责把数据写文件、编码或输出给分析模块。在 Camera Sun 中我们只需要照片输出因此核心是一条很短的链路// 文件路径Camera/CameraService.swift import AVFoundation final class CameraService { private let session AVCaptureSession() private let photoOutput AVCapturePhotoOutput() }这里把session和photoOutput作为类的私有属性外部通过公开方法启动、切换摄像头、拍照避免把 AVFoundation 细节暴露给 SwiftUI 层。3.2 配置 Session 时的关键点配置AVCaptureSession时最需要关注的是sessionPreset和beginConfiguration()/commitConfiguration()配对使用。// 文件路径Camera/CameraService.swift片段 func configureSession() { session.beginConfiguration() session.sessionPreset .photo // 配置输入 guard let camera AVCaptureDevice.default(for: .video), let input try? AVCaptureDeviceInput(device: camera), session.canAddInput(input) else { return } session.addInput(input) // 配置输出 guard session.canAddOutput(photoOutput) else { return } session.addOutput(photoOutput) session.commitConfiguration() }其中sessionPreset .photo表示使用照片模式输出质量最高beginConfiguration()和commitConfiguration()必须成对调用它告诉系统我们要批量修改会话配置避免多次通知设备canAddInput/canAddOutput用来判断当前设备是否支持该输入输出。在模拟器上无法获取AVCaptureDevice.default(for: .video)所以这段代码会在真机上才能拿到相机设备。3.3 预览层把相机画面显示到 UI 上AVCaptureVideoPreviewLayer是 CALayer 的子类专门用于展示实时预览。它和AVCaptureSession绑定后可以在屏幕上显示当前相机画面。由于我们用 SwiftUI 开发需要把UIViewRepresentable包一层。核心思路是创建一个UIView在内部设置AVCaptureVideoPreviewLayer然后通过UIViewRepresentable暴露给 SwiftUI。// 文件路径Camera/CameraPreviewView.swift import SwiftUI import AVFoundation struct CameraPreviewView: UIViewRepresentable { let session: AVCaptureSession func makeUIView(context: Context) - PreviewUIView { let view PreviewUIView() view.previewLayer.session session view.previewLayer.videoGravity .resizeAspectFill return view } func updateUIView(_ uiView: PreviewUIView, context: Context) { uiView.previewLayer.session session } } final class PreviewUIView: UIView { override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self } var previewLayer: AVCaptureVideoPreviewLayer { layer as! AVCaptureVideoPreviewLayer } }这里把UIView.layerClass重写为AVCaptureVideoPreviewLayer这样在makeUIView里就能直接拿到previewLayer。videoGravity设置为resizeAspectFill让预览画面像系统相机一样铺满整个取景区域。3.4 手动对焦与曝光补偿“修复”原生相机的一个重要方向就是把 iOS 相机的自动对焦、曝光控制打开一个手动窗口。AVCaptureDevice暴露了相关的属性focusMode自动对焦模式.continuousAutoFocus、.autoFocus、.lockedexposureMode曝光模式.continuousAutoExposure、.autoExpose、.lockedexposureTargetBias曝光补偿值范围由设备决定单位是 EVsetFocusPointOfInterest设置对焦点配合isFocusPointOfInterestSupported判断。例如我们想实现“点击画面中对焦并锁定曝光”可以这样做// 文件路径Camera/CameraService.swift片段 func focusAndLock(at point: CGPoint, in viewSize: CGSize) { guard let device AVCaptureDevice.default(for: .video) else { return } try? device.lockForConfiguration() let pointOfInterest CGPoint(x: point.y / viewSize.height, y: 1 - point.x / viewSize.width) if device.isFocusPointOfInterestSupported { device.focusPointOfInterest pointOfInterest device.focusMode .autoFocus } if device.isExposurePointOfInterestSupported { device.exposurePointOfInterest pointOfInterest device.exposureMode .continuousAutoExposure } device.unlockForConfiguration() }这里的坐标转换比较关键。SwiftUI 视图收到的point是左上角原点的坐标系而 AVFoundation 使用的pointOfInterest是横轴向右、纵轴从底部向上的归一化坐标。所以在把点击位置传给相机之前需要做一次转换。设置曝光补偿比较简单func setExposureBias(_ value: Float) { guard let device AVCaptureDevice.default(for: .video) else { return } try? device.lockForConfiguration() device.exposureTargetBias min(max(value, device.minExposureTargetBias), device.maxExposureTargetBias) device.unlockForConfiguration() }注意exposureTargetBias是有范围的不要直接赋值给一个超出范围的数。iOS 设备支持的范围通常在-8.0到8.0之间但真机型号不同会有差异因此最好读取minExposureTargetBias和maxExposureTargetBias后做一次 clamp。3.5 拍照输出拍照时通过AVCapturePhotoOutput.capturePhoto(with:delegate:)触发并在AVCapturePhotoCaptureDelegate回调中拿到照片数据。// 文件路径Camera/CameraService.swift片段 available(iOS 17.0, *) final class CameraService: NSObject, AVCapturePhotoCaptureDelegate { private var photoData: Data? func capturePhoto() { var settings AVCapturePhotoSettings() if photoOutput.availablePhotoCodecTypes.contains(.hevc) { settings AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.hevc]) } photoOutput.capturePhoto(with: settings, delegate: self) } func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { guard let data photo.fileDataRepresentation() else { return } // 在这里保存照片或传给上层处理 savePhoto(data) } }上面的savePhoto方法在实际项目中可以写入相册也可以只回调给 ViewModel。如果要在应用内处理 RAW可以在AVCapturePhotoSettings中设置rawPhotoPixelFormatType但并非所有设备都支持 RAW 拍摄需要先检查availableRawPhotoPixelFormatTypes。4. 完整实战案例实现一个精简版 Camera Sun这一节我们从头实现一个精简版 Camera Sun。功能包括相机预览切换前后摄像头拖动滑块调整曝光补偿点击拍照拍照后保存至系统相册。4.1 创建项目结构用 Xcode 新建一个 iOS App 项目命名为CameraSunInterface 选择 SwiftUI语言选择 Swift。创建完成后把默认生成的ContentView.swift整理成如下结构CameraSun ├── CameraSunApp.swift ├── ContentView.swift ├── Camera/ │ ├── CameraService.swift │ ├── CameraPreviewView.swift │ └── CameraControlPanel.swift如果你是用 Xcode 的 File New File 新建文件记得勾选 Target 为CameraSun。4.2 配置相机权限iOS 应用如果想访问相机硬件必须在Info.plist中声明用途描述。否则调用AVCaptureDevice.requestAccess时会直接崩溃或者被系统拒绝。此时 Info.plist 是 Xcode 自动生成的。在文件里添加一项keyNSCameraUsageDescription/key string需要访问您的相机用于展示实时预览并拍摄照片/string在 iOS 17 及以后也可以使用 Swift 宏或者通过 Target 的 Info 页直接添加。只要最终 Info.plist 中出现NSCameraUsageDescription即可。4.3 编写 CameraService接着实现负责管理AVCaptureSession的服务类。为了让状态能够在 SwiftUI 中被观察我会把部分状态标记为Published。// 文件路径Camera/CameraService.swift import AVFoundation import UIKit available(iOS 17.0, *) final class CameraService: NSObject, ObservableObject { Published var isRecording false Published var currentZoom: CGFloat 1.0 Published var exposureBias: Float 0.0 let session AVCaptureSession() private let photoOutput AVCapturePhotoOutput() private var currentInput: AVCaptureDeviceInput? private var isConfigured false private var usingFrontCamera false override init() { super.init() checkAuthorization() } // 请求相机权限 private func checkAuthorization() { switch AVCaptureDevice.authorizationStatus(for: .video) { case .authorized: configureSession() case .notDetermined: AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in DispatchQueue.main.async { if granted { self?.configureSession() } } } default: break } } // 配置采集会话 private func configureSession() { guard !isConfigured else { return } session.beginConfiguration() session.sessionPreset .photo guard let videoDevice AVCaptureDevice.default(for: .video), let videoInput try? AVCaptureDeviceInput(device: videoDevice), session.canAddInput(videoInput), session.canAddOutput(photoOutput) else { return } session.addInput(videoInput) session.addOutput(photoOutput) currentInput videoInput session.commitConfiguration() isConfigured true } // 启动会话让预览层开始显示画面 func startSession() { DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let self, !self.session.isRunning else { return } self.session.startRunning() } } // 停止会话 func stopSession() { DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let self, self.session.isRunning else { return } self.session.stopRunning() } } // 切换前后摄像头 func switchCamera() { guard let currentInput else { return } session.beginConfiguration() session.removeInput(currentInput) let deviceType: AVCaptureDevice.DeviceType usingFrontCamera ? .builtInWideAngleCamera : .builtInTrueDepthCamera let position: AVCaptureDevice.Position usingFrontCamera ? .back : .front guard let newDevice AVCaptureDevice.default(deviceType, for: .video, position: position), let newInput try? AVCaptureDeviceInput(device: newDevice), session.canAddInput(newInput) else { session.commitConfiguration() return } session.addInput(newInput) currentInput newInput usingFrontCamera.toggle() session.commitConfiguration() } // 拍照 func capturePhoto() { let settings: AVCapturePhotoSettings if photoOutput.availablePhotoCodecTypes.contains(.hevc) { settings AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.hevc]) } else { settings AVCapturePhotoSettings() } photoOutput.capturePhoto(with: settings, delegate: self) } // 设置曝光补偿 func setExposureBias(_ value: Float) { guard let device currentInput?.device else { return } try? device.lockForConfiguration() device.exposureTargetBias min(max(value, device.minExposureTargetBias), device.maxExposureTargetBias) device.unlockForConfiguration() exposureBias device.exposureTargetBias } } available(iOS 17.0, *) extension CameraService: AVCapturePhotoCaptureDelegate { func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { guard let data photo.fileDataRepresentation(), let image UIImage(data: data) else { return } UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) } }这个服务类的关键点权限处理放在checkAuthorization中避免在未授权时强行配置会话配置会话之前用isConfigured做保护避免重复配置使用AVCaptureDeviceInput替代AVCaptureDevice直接操作输入和会话解耦切换摄像头时先移除旧的input再添加新的input整个过程放在beginConfiguration()和commitConfiguration()之间。4.4 编写相机预览视图CameraPreviewView和前面拆解的一样使用UIViewRepresentable桥接 SwiftUI// 文件路径Camera/CameraPreviewView.swift import SwiftUI import AVFoundation struct CameraPreviewView: UIViewRepresentable { let session: AVCaptureSession func makeUIView(context: Context) - PreviewUIView { let view PreviewUIView() view.previewLayer.session session view.previewLayer.videoGravity .resizeAspectFill return view } func updateUIView(_ uiView: PreviewUIView, context: Context) { uiView.previewLayer.session session } } final class PreviewUIView: UIView { override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self } var previewLayer: AVCaptureVideoPreviewLayer { layer as! AVCaptureVideoPreviewLayer } }4.5 编写控制面板控制面板包含切换摄像头按钮、拍照按钮、曝光补偿滑块。// 文件路径Camera/CameraControlPanel.swift import SwiftUI struct CameraControlPanel: View { ObservedObject var cameraService: CameraService var body: some View { VStack { HStack { Button(切换摄像头) { cameraService.switchCamera() } .foregroundColor(.white) .padding() } Spacer() VStack(spacing: 16) { Text(曝光补偿: \(String(format: %.2f, cameraService.exposureBias)) EV) .foregroundColor(.white) Slider(value: Binding( get: { cameraService.exposureBias }, set: { newValue in cameraService.setExposureBias(Float(newValue)) } ), in: -4...4) .padding(.horizontal) } Button(action: { cameraService.capturePhoto() }, label: { Circle() .fill(Color.white) .frame(width: 70, height: 70) .overlay( Circle().stroke(Color.black.opacity(0.6), lineWidth: 4) ) }) } .padding() .background(Color.black.opacity(0.3)) } }上面滑块的范围我写死为-4...4。实际真机上的minExposureTargetBias和maxExposureTargetBias不一定刚好是 ±4建议根据支持范围动态调整。为了让示例更简单这里先固定范围读者可以在setExposureBias内做 clamp。4.6 组装主视图最后在ContentView中把预览层和控制面板组合起来。// 文件路径ContentView.swift import SwiftUI available(iOS 17.0, *) struct ContentView: View { StateObject private var cameraService CameraService() var body: some View { ZStack { CameraPreviewView(session: cameraService.session) .ignoresSafeArea() CameraControlPanel(cameraService: cameraService) } .onAppear { cameraService.startSession() } .onDisappear { cameraService.stopSession() } } }StateObject保证CameraService在视图生命周期内只创建一次。onAppear启动相机会话onDisappear停止会话避免页面退出后还在消耗相机资源。4.7 运行与验证在真机上运行项目。首次启动时系统会弹出相机权限申请弹窗点击“允许”后预览画面出现。随后操作验证点击“切换摄像头”预览画面应从后置摄像头切到前置摄像头拖动“曝光补偿”滑块画面明暗应随着 EV 值的调低或调高发生变化点击白色圆形拍照按钮系统相册中新增一张照片说明照片输出链路正常。需要注意模拟器上无法弹出真机预览也不会显示相机画面所以尽量使用真机调试。如果真机上出现黑屏优先检查权限是否允许以及session.startRunning()是否被调用。可以参考第 5 节的排查清单。5. 常见问题与排查思路5.1 黑屏 / 无画面问题现象项目运行后预览区域是黑屏没有画面。可能原因没有NSCameraUsageDescription权限描述权限弹窗都没出现权限未授权相机会话没有启动PreviewUIView没有正确绑定AVCaptureSession。排查步骤检查 Info.plist 中是否存在NSCameraUsageDescription在checkAuthorization()处打日志确认权限状态确认cameraService.startSession()在onAppear中被调用确认CameraPreviewView的session和CameraService.session是同一个实例。解决方案把 session 改为延迟配置并在startSession()中判断isConfigured如果未配置则先配置再启动。同时建议把startSession()放到合适的时机确保权限授权完成后再执行。5.2 权限弹窗已允许但设置界面拍照崩溃问题现象第一次允许后可以拍照切到后台再回到 App拍照闪退。可能原因相机会话在进入后台时被系统中断回到前台没有恢复或者AVCapturePhotoOutput代理回调中访问了已被释放的资源。排查方式在AppDelegate或SceneDelegate中监听UIApplication.didEnterBackgroundNotification和willEnterForegroundNotification分别停止和重新启动 session。解决方案建议在onDisappear中停止会话在onAppear中重新启动并且保证 delegate 回调在主线程处理 UI 操作。5.3 曝光补偿滑块不生效问题现象拖动滑块画面对亮度没有任何变化。可能原因没有调用lockForConfiguration()exposureTargetBias超出设备支持范围当前设备的曝光模式不是.continuousAutoExposure或.locked部分设备需要先设置曝光模式。处理思路在设置exposureTargetBias前先读取minExposureTargetBias和maxExposureTargetBias并做 clamp如果画面仍不亮可以尝试把exposureMode设置为.continuousAutoExposure然后再设置exposureTargetBias。5.4 照片保存到相册失败问题现象拍照后相册中没有新照片。可能原因相册权限未声明写入系统相册被拒绝fileDataRepresentation()返回 nil代理回调没有被调用。解决方案在 Info.plist 中添加NSPhotoLibraryAddUsageDescriptionkeyNSPhotoLibraryAddUsageDescription/key string需要将拍摄的照片保存到系统相册/string另外打印error信息确认回调是否走到didFinishProcessingPhoto。问题现象常见原因解决思路黑屏权限未授权 / 会话未启动检查权限描述配置并启动 session拍照崩溃生命周期未处理在后台/前台切换时停止/重启 session曝光补偿无效超出范围 / 未锁配置clamp 参数并检查曝光模式保存失败缺少相册权限添加 NSPhotoLibraryAddUsageDescription6. 最佳实践与工程建议6.1 会话配置要轻量尽量避免频繁改动AVCaptureSession的配置切换成本很高。在拍照或录制的过程中频繁执行beginConfiguration()、addInput、removeInput会导致画面卡顿甚至短暂黑屏。合理做法是在页面初始化时一次性配置好输入输出切换摄像头时才重新配置输入不要每次拍照都重建 session如果业务场景需要实时变焦优先通过videoZoomFactor实现不要重新创建输入。6.2 权限处理要前置到页面加载之前相机权限是用户隐私保护的第一道门。建议在进入相机页面前先检查AVCaptureDevice.authorizationStatus(for: .video)。如果用户已经明确拒绝不要在页面中不断弹出引导弹窗而应该在 UI 上展示一个“去设置开启”的入口。6.3 注意相机生命周期相机硬件是系统共享资源。当 App 进入后台时其他应用可能也需要相机因此必须及时停止会话。前后台切换的推荐实现是NotificationCenter.default.addObserver( forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main ) { [weak self] _ in self?.stopSession() } NotificationCenter.default.addObserver( forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main ) { [weak self] _ in self?.startSession() }6.4 内存管理与线程安全AVFoundation 的相机回调不一定在主线程。在photoOutput(_:didFinishProcessingPhoto:error:)中处理照片数据时如果需要更新 UI要切到主线程。如果要做图像识别、滤镜处理等耗时操作建议放到后台队列避免阻塞协流。在处理耗时操作时可以用单独的队列private let processingQueue DispatchQueue(label: com.camerasun.processing)6.5 注意隐私与合规自定义相机应用涉及隐私问题特别是如果你把 Camera Sun 做成了需要用户上传照片或视频的 App需要明确告知用户数据用途照片默认只保存在相册不上传服务端如果做人脸相关识别需要符合平台隐私政策不要通过摄像头非法采集用户周边环境。6.6 参数控制要符合设备能力不同 iPhone 机型对相机的支持差异很大前置摄像头不一定支持 RAW 输出builtInTrueDepthCamera只在前置可用变焦倍数取决于物理摄像头数量部分设备不支持高帧率录制。在给用户开放手动参数之前最好先通过AVCaptureDevice的能力检测动态禁用不支持的控件。比如if device.isExposureModeSupported(.locked) { // 才提供曝光锁定按钮 }6.7 模块化设计为扩展保留空间Camera Sun 项目虽然只是一个示例但建议从 SDK 设计的角度来组织代码CameraService只负责相机采集和输出CameraControlPanel只负责 UI 交互PhotoStore负责照片保存、相册权限、文件管理CameraSettings负责曝光、对焦、变焦等参数状态。这样后续要接入 Core ML 或自定义滤镜只改中间层就够了。7. 总结与学习路线Camera Sun 这个项目本质上是一个通过 AVFoundation 重新定制 iPhone 相机体验的实践案例。我们从原生相机的不足出发拆解了相机采集链路的核心概念然后完整实现了一个包括权限申请、会话配置、预览层、曝光补偿、切换摄像头、拍照保存的精简相机应用。通过这个案例你至少掌握了AVCaptureSession的输入输出配置方式SwiftUI 中如何桥接AVCaptureVideoPreviewLayer相机权限申请和信息配置文件的作用曝光补偿、对焦点等手动参数的设置方法相机页面生命周期管理的基本思路。下一步可以继续扩展的方向是接入 Vision 框架实现人脸检测或文字识别结合 Metal / Core Image在预览层叠加实时滤镜使用AVCaptureMovieFileOutput支持视频录制通过AVCaptureVideoDataOutput获取逐帧像素做 AI 推理。建议你在真机上把每个功能拆开单独调试尤其是“切换摄像头”和“曝光补偿”这两个操作可以好好观察beginConfiguration()/commitConfiguration()带来的画面变化。写相机应用最忌讳“在模拟器上验证”所以有条件就多拿几台真机试试。如果本文对你有帮助可以收藏备用。后面我会继续分享 SwiftUI 相机应用的进阶优化比如实时滤镜、RAW 输出和视频录制欢迎关注。