iPhone 16 拍照按钮适配
10/14/2024
拍照按钮功能简述
- 按钮的按下与抬起,在系统原生相机中的响应事件为开始录像或拍摄照片;
- 按钮的轻按和手势滑动:
- 轻按分为单击和双击,单击时可以唤起侧边栏或选中功能菜单的功能,双击时侧边栏退回到上一层级;
- 滑动时可以切换菜单中的功能或变更滑杆数值。
适配的条件和准备工作
- 以下所有适配在目前 iOS 18.0 SDK 下只能在摄像头启动的场景下有效,文档中特别提到
This API is for media capture use cases only.
; - 按钮的按下与抬起需要通过
AVCaptureEventInteraction
监听,interaction 需要添加在一个与采集相关的 view 上,个人认为添加在管理拍摄的 viewController.view 上即可; - 按钮的轻按和手势滑动需要根据功能设置对应的 Slider 或 Picker,这些组件都继承自
AVCaptureControl
,初始化时需要指定使用的AVCaptureDevice
,并绑定在当前正在采集的AVCaptureSession
上。 - 为了接收
AVCaptureControl
的回调事件,需要指定一个接收代理的对象和队列,并在对象内实现AVCaptureSessionControlsDelegate
中的方法。
Demo 代码和说明
- 如前文第 4 条,在相机采集类中添加代理:
class CustomCameraCapture: NSObject { // 其他属性声明... lazy var captureSession: AVCaptureSession = AVCaptureSession() private lazy var interactionQueue: DispatchQueue = DispatchQueue(label: "MyInteractionQueue") // 其他方法... func startCapture() { // 处理其他开始录制的逻辑... if #available(iOS 18.0, *) { captureSession.setControlsDelegate(self, queue: interactionQueue) } } } // 实现 AVCaptureSessionControlsDelegate 中的方法,这些方法的目的是通知 AVCaptureControl 的生命周期 extension CustomCameraCapture: AVCaptureSessionControlsDelegate { func sessionControlsDidBecomeActive(_ session: AVCaptureSession) { debugPrint("[AVCaptureSessionControlsDelegate] sessionControlsDidBecomeActive") } func sessionControlsWillEnterFullscreenAppearance(_ session: AVCaptureSession) { debugPrint("[AVCaptureSessionControlsDelegate] sessionControlsWillEnterFullscreenAppearance") } func sessionControlsWillExitFullscreenAppearance(_ session: AVCaptureSession) { debugPrint("[AVCaptureSessionControlsDelegate] sessionControlsWillExitFullscreenAppearance") } func sessionControlsDidBecomeInactive(_ session: AVCaptureSession) { debugPrint("[AVCaptureSessionControlsDelegate] sessionControlsDidBecomeInactive") } }
- 在相机采集类中提供一个给 viewController 设置
AVCaptureControl
的方法:
func handleInteractionsAndControls(on view: UIView) { // 虽然 AVCaptureEventInteraction 是 iOS 17.2 SDK 引入的,为了逻辑统一按照 iOS 18.0 做版本检查 guard #available(iOS 18.0, *) else { return } guard let device = self.currentDevice else { return } self.clearInteractionsAndControls(on: view) // 设置 AVCaptureEventInteraction,根据 event.phase 确定按钮按下/抬起状态 let interaction = AVCaptureEventInteraction { event in debugPrint("[AVCaptureEventInteraction] Event Phase: \(event.phase)") } view.addInteraction(interaction) // 设置系统原生的相机缩放值滑杆,回调 zoomFactor 为放大倍率,这个返回值可能有一些问题,将在后文中讨论 let systemZoomSlider = AVCaptureSystemZoomSlider(device: device) { zoomFactor in debugPrint("[AVCaptureSystemZoomSlider] Zoom Factor: \(zoomFactor)") } // 设置系统原生的相机曝光值滑杆,回调 zoomFactor 为曝光度 let systemBiasSlider = AVCaptureSystemExposureBiasSlider(device: device) { val in debugPrint("[AVCaptureSystemExposureBiasSlider] Value: \(val)") } // 设置自定义的滑杆,回调 val 为滑杆数值,初始化时的参数 symbolName 对应 SF Symbols 中的图标名称,in 后的参数为可调节的数值范围 // 滑杆一般用于连续的数值选择,在原生相机中的使用场景是缩放、曝光、景深等 let customSlider = AVCaptureSlider("Focus", symbolName: "scope", in: 0...2) customSlider.setActionQueue(self.interactionQueue) { val in debugPrint("[AVCaptureSlider] Value: \(val)") } // 设置自定义的选取框,回调 index 为选中的元素下标,初始化时的参数 symbolName 对应 SF Symbols 中的图标名称,localizedIndexTitles 为可选择的选项 // 选取框一般用于离散的元素选择,在原生相机中的使用场景是选择滤镜或切换摄像头等 let customPicker = AVCaptureIndexPicker("Filters", symbolName: "camera.filters", localizedIndexTitles: ["Filter 1", "Filter 2", "Filter 3", "Filter 4"]) customPicker.setActionQueue(self.interactionQueue) { index in debugPrint("[AVCaptureIndexPicker] Index: \(index)") } // 开启 session 配置self.captureSession.beginConfiguration() let controls = [systemZoomSlider, systemBiasSlider, customSlider, customPicker] for control in controls { // 添加 control 前务必检查当前是否可以添加,直接添加可能存在个数溢出或报 NSInvalidArgumentException 异常 if self.captureSession.canAddControl(control) { self.captureSession.addControl(control) } } // 提交 session 配置 self.captureSession.commitConfiguration() } private func clearInteractionsAndControls(on view: UIView) { guard #available(iOS 18.0, *) else { return } view.interactions.forEach({ view.removeInteraction($0) }) self.captureSession.controls.forEach({ self.captureSession.removeControl($0) }) }
- 在使用相机采集的 viewController 上调用方法:
self.customCamera?.handleInteractionsAndControls(on: self.view)
一些讨论
AVCaptureControl
目前是黑盒的,我们只能按要求初始化并获取回调,无法自定义内部行为。特别说明这个问题是因为我个人体验下来,拍照按钮的滑动手感可能由于按钮的触控采样率相对屏幕较低,导致体感上并不非常跟手,但开发者没有办法优化内部的数据采集;AVCaptureSystemZoomSlider
回调的缩放值和滑杆上显示的数值不同,测试发现滑杆上显示的范围是1.0~5.0,但实际返回的数值是1.0~6.0,且两者之间符合线性关系 y=1.25x-0.25。这个问题在苹果开发者论坛上也有帖子讨论但目前没有官方回复。
参考文档
Enhancing your app experience with the Camera Control
Human Interface Guidelines / Camera Control
发表评论