🎬 为什么Camera Control是移动摄影的未来?#
想象一下:你在最喜欢的乐队的演唱会上。人群从四面八方挤压,双手被占用,你正试图拍下完美的照片。以前,你不得不笨拙地点击屏幕,冒着手机掉落的风险。现在,只需按下iPhone 16的侧边按钮一次 — 瞬间就被捕捉了。
这不是科幻小说。这是Camera Control — Apple近年来在移动摄影领域最重要的创新之一。
你将学到什么#
在本指南中,我们将从零开始走完Camera Control集成的完整旅程:
- Camera Control架构如何工作以及为什么这样工作
- 从零开始的项目设置步骤
- 创建锁屏相机扩展(在设置中显示所必需的)
- 添加系统缩放和曝光控制
- 处理快门按钮事件
- UIKit和SwiftUI的集成
- 解决常见问题和调试
📱 什么是Camera Control及其工作原理?#
Camera Control不仅仅是"另一个按钮"。它是一个将硬件、软件API和直观用户界面结合在一起的完整生态系统。
硬件基础#
在iPhone 16及更新型号中,侧边按钮配备了特殊传感器:
- Force Touch — 检测按压力度
- 电容感应 — 识别触摸和滑动
- 触觉反馈 — 提供触觉响应
这种组合使按钮可以作为:
- 相机快门(完全按下)
- 设置滑块(在按钮上滑动)
- 模式切换器(双击)
Camera Control的三个主要功能#
- 应用启动 — Camera Control可以直接从锁屏打开你的应用
- 快门按钮 — 物理按压用于拍照或开始/停止视频
- 参数调整 — 在按钮上滑动以调整缩放、曝光和其他参数
🎯 要求和兼容性#
在编写代码之前,确保你的项目满足要求。
系统要求#
| 组件 | 最低版本 |
|---|---|
| iOS | 18.0+ |
| 设备 | iPhone 16, 16 Plus, 16 Pro, 16 Pro Max |
| Xcode | 16.0+ |
| Swift | 5.9+ |
所需框架#
import AVFoundation // 相机工作的主要框架
import AVKit // 用于AVCaptureEventInteraction
import Photos // 用于将照片保存到相册代码中检查兼容性#
使用前务必检查Camera Control的可用性:
func checkCameraControlSupport() -> Bool {
// 检查iOS版本
guard #available(iOS 18.0, *) else {
print("❌ 需要iOS 18.0或更高版本")
return false
}
// 检查设备支持
let session = AVCaptureSession()
guard session.supportsControls else {
print("❌ 设备不支持Camera Control")
return false
}
print("✅ Camera Control完全支持")
return true
}⚠️ 重要:
supportsControls属性仅在iPhone 16及更新设备上返回true。在模拟器和旧设备上始终为false。
🏗️ 项目架构#
推荐的文件结构#
CameraControlApp/
├── CameraControlApp.xcodeproj
├── CameraControlApp/
│ ├── App/
│ │ ├── CameraControlApp.swift // 入口点 (SwiftUI)
│ │ └── AppDelegate.swift // (可选,用于UIKit)
│ ├── Camera/
│ │ ├── CameraController.swift // 主相机逻辑
│ │ ├── CameraPreviewView.swift // UIKit预览
│ │ └── CameraModel.swift // SwiftUI的ObservableObject
│ ├── Views/
│ │ ├── CameraView.swift // SwiftUI相机视图
│ │ └── CameraViewController.swift // UIKit控制器
│ └── Extensions/
│ └── AVCaptureSession+Extensions.swift
├── LockScreenCameraExtension/ // 锁屏扩展
│ ├── LockScreenCameraExtension.swift
│ └── Info.plist
└── Tests/
└── CameraControlTests.swift🔧 步骤1:创建CameraController#
CameraController是管理所有相机和Camera Control逻辑的类。
import AVFoundation
import AVKit
import Photos
/// 支持Camera Control的主相机控制器
class CameraController: NSObject, ObservableObject {
// MARK: - 公共属性
/// 当前缩放倍数
@Published var currentZoomFactor: CGFloat = 1.0
/// 当前曝光偏移
@Published var currentExposureBias: Float = 0.0
/// Camera Control叠加层是否激活
@Published var isCameraControlActive: Bool = false
/// 最后捕获的图像(用于预览)
@Published var lastCapturedImage: UIImage?
/// 相机权限状态
@Published var permissionStatus: AVAuthorizationStatus = .notDetermined
// MARK: - 内部属性
/// 捕获会话 — AVFoundation的核心对象
let session = AVCaptureSession()
/// 会话配置队列(绝对不要在主线程上!)
private let sessionQueue = DispatchQueue(
label: "com.yourapp.camera.session",
qos: .userInitiated
)
/// 照片输出
private let photoOutput = AVCapturePhotoOutput()
/// 相机输入
private var deviceInput: AVCaptureDeviceInput?
/// KVO观察者存储
private var observations: [NSKeyValueObservation] = []
// MARK: - 初始化
override init() {
super.init()
checkPermissions()
}
// MARK: - 权限处理
/// 检查并请求相机权限
func checkPermissions() {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
permissionStatus = .authorized
configureSession()
case .notDetermined:
sessionQueue.suspend()
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
guard let self = self else { return }
DispatchQueue.main.async {
self.permissionStatus = granted ? .authorized : .denied
}
if granted {
self.configureSession()
}
self.sessionQueue.resume()
}
case .denied, .restricted:
permissionStatus = .denied
print("⚠️ 相机访问被拒绝")
@unknown default:
break
}
}
// MARK: - 会话配置
/// 配置AVCaptureSession
private func configureSession() {
sessionQueue.async { [weak self] in
guard let self = self else { return }
self.session.beginConfiguration()
self.session.sessionPreset = .photo
// 配置相机输入
guard self.setupCameraInput() else {
self.session.commitConfiguration()
return
}
// 配置照片输出
guard self.setupPhotoOutput() else {
self.session.commitConfiguration()
return
}
// 配置Camera Control (iOS 18+)
if #available(iOS 18.0, *) {
self.setupCameraControls()
}
self.session.commitConfiguration()
self.session.startRunning()
print("✅ 相机会话已启动")
}
}
/// 配置相机输入
private func setupCameraInput() -> Bool {
guard let camera = AVCaptureDevice.default(
.builtInWideAngleCamera,
for: .video,
position: .back
) else {
print("❌ 相机不可用")
return false
}
do {
let input = try AVCaptureDeviceInput(device: camera)
if session.canAddInput(input) {
session.addInput(input)
deviceInput = input
setupDeviceObservers(device: camera)
return true
} else {
print("❌ 无法添加相机输入")
return false
}
} catch {
print("❌ 错误: \(error)")
return false
}
}
/// 配置照片输出
private func setupPhotoOutput() -> Bool {
guard session.canAddOutput(photoOutput) else {
print("❌ 无法添加照片输出")
return false
}
session.addOutput(photoOutput)
photoOutput.isHighResolutionCaptureEnabled = true
if let connection = photoOutput.connection(with: .video),
connection.isVideoStabilizationSupported {
connection.preferredVideoStabilizationMode = .auto
}
return true
}
/// 设置设备更改观察者
private func setupDeviceObservers(device: AVCaptureDevice) {
// 观察缩放变化
let zoomObserver = device.observe(
\.videoZoomFactor,
options: [.new]
) { [weak self] device, change in
guard let newValue = change.newValue else { return }
DispatchQueue.main.async {
self?.currentZoomFactor = newValue
}
}
observations.append(zoomObserver)
// 观察曝光变化
let exposureObserver = device.observe(
\.exposureTargetBias,
options: [.new]
) { [weak self] device, change in
guard let newValue = change.newValue else { return }
DispatchQueue.main.async {
self?.currentExposureBias = newValue
}
}
observations.append(exposureObserver)
}
// MARK: - 照片捕获
/// 拍照
func capturePhoto() {
guard let connection = photoOutput.connection(with: .video),
connection.isEnabled else {
print("❌ 相机连接未激活")
return
}
let settings = AVCapturePhotoSettings()
settings.flashMode = .auto
settings.isHighResolutionPhotoEnabled = true
photoOutput.capturePhoto(with: settings, delegate: self)
print("📸 照片捕获已开始")
}
// MARK: - 生命周期
func startSession() {
sessionQueue.async { [weak self] in
guard let self = self, !self.session.isRunning else { return }
self.session.startRunning()
}
}
func stopSession() {
sessionQueue.async { [weak self] in
guard let self = self, self.session.isRunning else { return }
self.session.stopRunning()
}
}
deinit {
observations.removeAll()
stopSession()
}
}🎛️ 步骤2:添加Camera Control (iOS 18+)#
// MARK: - Camera Control设置 (iOS 18+)
@available(iOS 18.0, *)
extension CameraController: AVCaptureSessionControlsDelegate {
/// 配置Camera Control
func setupCameraControls() {
// 检查支持
guard session.supportsControls else {
print("⚠️ Camera Control不支持")
return
}
guard let device = deviceInput?.device else {
print("❌ 找不到相机设备")
return
}
// 移除现有控件
for control in session.controls {
session.removeControl(control)
}
var controls: [AVCaptureControl] = []
// 1. 系统缩放滑块
let zoomSlider = AVCaptureSystemZoomSlider(
device: device
) { [weak self] zoomFactor in
DispatchQueue.main.async {
self?.currentZoomFactor = zoomFactor
}
print("🔍 缩放: \(zoomFactor)x")
}
controls.append(zoomSlider)
// 2. 系统曝光滑块
let exposureSlider = AVCaptureSystemExposureBiasSlider(
device: device
) { [weak self] bias in
DispatchQueue.main.async {
self?.currentExposureBias = bias
}
print("☀️ 曝光: \(bias)")
}
controls.append(exposureSlider)
// 将控件添加到会话
for control in controls {
if session.canAddControl(control) {
session.addControl(control)
print("✅ 已添加: \(type(of: control))")
}
}
// 设置委托
session.setControlsDelegate(self, queue: sessionQueue)
print("✅ Camera Control已配置")
}
// MARK: - AVCaptureSessionControlsDelegate
func sessionControlsDidBecomeActive(_ session: AVCaptureSession) {
DispatchQueue.main.async { [weak self] in
self?.isCameraControlActive = true
print("🎮 Camera Control已激活")
}
}
func sessionControlsDidBecomeInactive(_ session: AVCaptureSession) {
DispatchQueue.main.async { [weak self] in
self?.isCameraControlActive = false
print("🎮 Camera Control已停用")
}
}
}📸 步骤3:照片捕获处理#
// MARK: - AVCapturePhotoCaptureDelegate
extension CameraController: AVCapturePhotoCaptureDelegate {
func photoOutput(
_ output: AVCapturePhotoOutput,
didFinishProcessingPhoto photo: AVCapturePhoto,
error: Error?
) {
if let error = error {
print("❌ 错误: \(error.localizedDescription)")
return
}
guard let imageData = photo.fileDataRepresentation(),
let image = UIImage(data: imageData) else {
print("❌ 无法获取图像")
return
}
DispatchQueue.main.async { [weak self] in
self?.lastCapturedImage = image
}
saveToPhotoLibrary(imageData: imageData)
print("✅ 照片已捕获")
}
private func saveToPhotoLibrary(imageData: Data) {
PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in
guard status == .authorized else {
print("⚠️ 没有保存权限")
return
}
PHPhotoLibrary.shared().performChanges {
let request = PHAssetCreationRequest.forAsset()
request.addResource(with: .photo, data: imageData, options: nil)
} completionHandler: { success, error in
if success {
print("✅ 已保存到相册")
}
}
}
}
}🔐 步骤7:锁屏相机扩展#
这是一个关键步骤! 没有锁屏扩展,你的应用不会出现在Camera Control设置中。
创建扩展目标#
- 在Xcode中: File → New → Target
- 选择 "Locked Camera Capture Extension"
- 命名:
YourAppLockScreenCamera - 点击 Create
扩展实现#
import LockedCameraCapture
import AVFoundation
import Photos
import UIKit
@main
class LockScreenCameraExtension: NSObject, LockedCameraCaptureExtension {
private var captureSession: LockedCameraCaptureSession?
private var photoOutput: LockedCameraCapturePhotoOutput?
func beginCapture(
with device: LockedCameraCaptureDevice,
completion: @escaping (Error?) -> Void
) {
print("📷 锁屏相机: 开始捕获")
captureSession = LockedCameraCaptureSession()
photoOutput = LockedCameraCapturePhotoOutput()
guard let session = captureSession,
let output = photoOutput else {
completion(LockScreenCameraError.initializationFailed)
return
}
do {
try session.addInput(device)
try session.addOutput(output)
session.startRunning()
print("✅ 锁屏相机: 会话已启动")
completion(nil)
} catch {
print("❌ 锁屏相机: \(error)")
completion(error)
}
}
func endCapture(completion: @escaping (Error?) -> Void) {
captureSession?.stopRunning()
captureSession = nil
photoOutput = nil
completion(nil)
}
}
enum LockScreenCameraError: Error {
case initializationFailed
}⚠️ 常见问题和解决方案#
问题1:Camera Control不显示#
症状: 侧边按钮没有响应。
解决方案:
- 检查
session.supportsControls— 需要iPhone 16+ - 确保会话正在运行
- 预览必须在屏幕上可见
- 控件必须已添加
问题2:应用不在Camera Control设置中#
原因: 没有锁屏相机扩展。
解决方案: 创建扩展(步骤7)。
问题3:UI不更新#
原因: 更新不在主线程上。
// ✅ 正确
let zoomSlider = AVCaptureSystemZoomSlider(device: device) { [weak self] factor in
DispatchQueue.main.async {
self?.currentZoomFactor = factor
}
}🎯 最佳实践#
1. 始终检查兼容性#
if #available(iOS 18.0, *), session.supportsControls {
setupCameraControls()
} else {
showTraditionalControls()
}2. Camera Control激活时隐藏你的控件#
func sessionControlsDidBecomeActive(_ session: AVCaptureSession) {
DispatchQueue.main.async {
self.zoomSlider.isHidden = true
}
}📋 发布前检查清单#
- 锁屏相机扩展已创建
- Info.plist包含所有权限
- 代码处理不支持的情况
- UI在主线程更新
- 会话在后台线程配置
- 系统控件已添加
- AVCaptureEventInteraction已配置
- 在真实iPhone 16上测试
- 优雅降级有效
🎬 结论#
Camera Control是用户在iPhone上与相机交互方式的根本性变化。
关键点#
- 锁屏扩展是必需的 — 没有它,应用不会出现在设置中
- 检查
supportsControls— 仅在iPhone 16+上有效 - 使用系统控件 —
AVCaptureSystemZoomSlider和AVCaptureSystemExposureBiasSlider - 处理事件 — 快门按钮的
AVCaptureEventInteraction - 同步UI — 通过
AVCaptureSessionControlsDelegate委托 - 在真实设备上测试 — 模拟器不支持Camera Control
有用的链接:

