Highlight
iOS 26 让 H2 芯片的 AirPods 通过按柄点按远程触发相机捕获,已接入 AVCaptureEventInteraction 的 app 自动获得此能力,无需修改一行代码。
核心内容
第三方相机 app 一直有个尴尬的体验断层:用户按系统相机习惯的「按音量键拍照」「长按录像」「用 Camera Control 调焦」,到了第三方 app 里全部失灵。屏幕上那颗虚拟快门按钮,远不如 iPhone 侧边的物理按键来得自然。更尴尬的是自拍场景——把手机架远后,没有任何办法触发拍摄。
WWDC25 这场 Session 由 AVKit 团队的 Vitaliy 和相机体验团队的 Nick 联合讲解,覆盖两套 API:AVCaptureEventInteraction 把音量键、Action 按钮、Camera Control 的物理按压事件转成 app 内的 capture 回调,6 行 SwiftUI 代码就能让第三方 app 用上系统相机般的物理按键体验;AVCaptureControl 则把 iPhone 16 的 Camera Control 暴露成可编程的硬件接口,支持连续滑块、离散滑块、Picker 三种控件。iOS 26 在此基础上加了一项新能力:H2 芯片的 AirPods 按柄点按可以远程触发主捕获事件,已经接入 AVCaptureEventInteraction 的 app 直接获得此功能,无需任何代码改动。
详细内容
AVCaptureEventInteraction:物理按键映射到捕获
物理按钮分为 primary 和 secondary 两类(03:37)。音量下键、Action 按钮、Camera Control 触发 primary action;音量上键触发 secondary action。同一个按钮不能同时触发两个 handler。secondary handler 是可选的——不指定时,音量上键也走 primary。
每次按压会发出一个事件通知,包含三个 phase(03:12):
began:按下瞬间,适合预热相机对象cancelled:app 进入后台或捕获不可用时ended:松开瞬间,这才是实际触发拍照的时机
SwiftUI 通过 onCameraCaptureEvent modifier 接入。一个最简的相机 view 加上物理按键支持,只要 6 行新代码(06:14):
import AVKit
import SwiftUI
struct PhotoCapture: View {
let camera = CameraModel()
var body: some View {
VStack {
CameraView()
.onCameraCaptureEvent { event in
if event.phase == .ended {
camera.capturePhoto()
}
}
Button(action: camera.capturePhoto) {
Text("Take a photo")
}
}
}
}
关键点:
import AVKit:onCameraCaptureEvent由 AVKit 提供,不在 SwiftUI 默认 modifier 集合里.onCameraCaptureEvent { event in ... }挂在CameraView上,确保事件只在相机视图活跃时被消费event.phase == .ended才调用capturePhoto(),避免在began阶段过早触发- 屏幕上的
Button仍然保留——物理按键和虚拟按钮共用同一个camera.capturePhoto方法
注意:系统只把 capture event 发给「正在使用相机」的 app(04:22)。app 进后台或没有活跃的 AVCaptureSession,按键会回退到系统默认行为(调音量、启动相机)。一旦接入这个 API,必须响应所有收到的事件——不响应会让按钮变成哑键,体验很糟。
iOS 26:AirPods 按柄远程捕获
支持 H2 芯片的 AirPods 在 iOS 26 上获得远程相机捕获能力(06:38)。用户在「设置 → Remote Camera Capture」里选择「Press once」或「Press and hold」,然后在任何接入了 AVCaptureEventInteraction 的相机 app 中点按 AirPod 按柄即可触发主捕获。这个能力对开发者完全透明:已接入 API 的 app 自动获得。
因为用户拍照时可能没看屏幕,音频反馈变成关键。Apple 为此加了默认快门音,并提供了一个新的 API 让 app 自定义或禁用(09:13):
.onCameraCaptureEvent(defaultSoundDisabled: true) { event in
if event.phase == .ended {
if event.shouldPlaySound {
event.play(.cameraShutter)
}
}
camera.capturePhoto()
}
关键点:
defaultSoundDisabled: true关掉系统默认提示音,避免和 app 自己的音效叠加event.shouldPlaySound仅在事件由 AirPod 按柄触发时为true——音量键、Action 按钮、Camera Control 触发的事件都返回falseevent.play(.cameraShutter)播放系统快门音;也可以播放 app bundle 里自带的音频文件- 即使关掉默认音,也务必在 AirPod 触发路径上播放某种反馈音——用户没看屏幕,全靠听
AVCaptureControl:把 Camera Control 编程化
Camera Control 是 iPhone 16 侧边的物理控件,支持点按拍照、轻按调出预览、滑动调节、双击切换设置(11:02)。AVFoundation 把它抽象成 AVCaptureControl,分两类:
- Slider(数值):连续型可在范围内取任意值(如 zoom factor);离散型只取指定步进(如曝光补偿,±2 EV,1/3 步进)
- Picker(列表):每个 index 对应一个命名状态(如 Flash 的 On/Off,Photographic Style 的名字)
系统预定义了两个子类:AVCaptureSystemZoomSlider 和 AVCaptureSystemExposureBiasSlider,行为对齐系统相机。还有两个通用子类(AVCaptureSlider、AVCaptureIndexPicker),用来定义自己的控件。
加一个系统变焦滑块到 session 上(14:46):
captureSession.beginConfiguration()
// configure device inputs and outputs
if captureSession.supportsControls {
let zoomControl = AVCaptureSystemZoomSlider(device: device)
if captureSession.canAddControl(zoomControl) {
captureSession.addControl(zoomControl)
}
}
captureSession.commitConfiguration()
关键点:
captureSession.supportsControls先判断设备是否有 Camera Control(仅 iPhone 16 系列),不支持就跳过整段,避免在老设备上误用AVCaptureSystemZoomSlider(device: device)一行创建——系统控件直接拿到device引用,自动驱动videoZoomFactor,不用 app 手动设置canAddControl是必要检查:例如 device 已经关联了一个 zoom control 时,再加第二个会失败- 整段包在
beginConfiguration/commitConfiguration之间,按AVCaptureSession的标准事务模式提交
但只这样写会有同步问题:用户用 Camera Control 变焦后,UI 上的 pinch 手势模型还停留在旧值。修法是给 system control 传一个 action closure(15:40):
let zoomControl = AVCaptureSystemZoomSlider(device: device) { [weak self] zoomFactor in
self?.updateUI(zoomFactor: zoomFactor)
}
关键点:
- closure 在 main thread 上被回调(system control 的约定),可以直接更新 UI
[weak self]防止控件持有 view controller 形成循环引用- 已经用 KVO 监听
videoZoomFactor的项目可以省掉这个 closure,继续用原有机制
自定义控件:Reaction Effects Picker
App 自定义的 Picker,action callback 在你指定的 queue 上执行——典型做法是放到 session queue,因为它是管理 device 状态的隔离上下文(16:46):
let reactions = device.availableReactionTypes.sorted { $0.rawValue < $1.rawValue }
let titles = reactions.map { localizedTitle(reaction: $0) }
let picker = AVCaptureIndexPicker("Reactions", symbolName: "face.smiling.inverted",
localizedIndexTitles: titles)
picker.isEnabled = device.canPerformReactionEffects
picker.setActionQueue(sessionQueue) { index in
device.performEffect(for: reactions[index])
}
let controls: [AVCaptureControl] = [zoomControl, picker]
for control in controls {
if captureSession.canAddControl(control) {
captureSession.addControl(control)
}
}
关键点:
availableReactionTypes是无序集合,先sorted给 Picker 一个稳定顺序AVCaptureIndexPicker需要名称和 SF Symbol 图标——多控件场景下用户要靠图标识别这是哪个 Pickerpicker.isEnabled = device.canPerformReactionEffects:控件不可用时置 disabled 而非省略,否则系统会 fallback 到另一个控件,用户会困惑setActionQueue(sessionQueue):action 在 session queue 执行,因此device.performEffect调用与其它 device 配置串行,不会出现并发竞态maxControlsCount限制 session 上的控件总数,超过后canAddControl返回 false(16:46 提到)
核心启发
-
做什么:给已有相机 app 接入
onCameraCaptureEvent,自动获得 iOS 26 AirPods 远程拍照- 为什么值得做:6 行代码换来一个完整的远程触发场景(自拍、合影、运动场景),是 iOS 26 上少见的「零成本新功能」
- 怎么开始:在主拍摄 view 上挂
.onCameraCaptureEvent,把event.phase == .ended路由到现有的capturePhoto()方法,确保AVCaptureSession在前台时是 active 状态
-
做什么:自定义快门音,对 AirPods 触发场景做差异化反馈
- 为什么值得做:用户用 AirPods 拍照时不看屏幕,音效是唯一确认信号;默认提示音不一定贴合 app 的氛围(专业相机 vs. 滤镜 app 需要不同声音)
- 怎么开始:用
defaultSoundDisabled: true关掉默认音,在 closure 内判断event.shouldPlaySound决定是否播放——shouldPlaySound只在 AirPod 路径下为 true,其它路径不会重复发声
-
做什么:在 iPhone 16 上加一个 Photographic Style / 滤镜 Picker,挂到 Camera Control
- 为什么值得做:用户已经习惯在 Camera Control 上「轻按两下切控件 → 滑动选择 → 轻按确认」的肌肉记忆,自定义 Picker 可以直接复用这套交互
- 怎么开始:用
AVCaptureIndexPicker包装你的滤镜列表,setActionQueue(sessionQueue)把切换动作放到 session queue 上同步处理 device 配置;记得对不可用滤镜置isEnabled = false而非省略
-
做什么:把 zoom / 曝光补偿换成系统预定义控件,去掉自实现
- 为什么值得做:
AVCaptureSystemZoomSlider一行创建,行为完全对齐系统相机,包括步进、回弹、HUD 显示——自己写很难做到这种一致性 - 怎么开始:删掉自定义的 zoom 控件实现,换成
AVCaptureSystemZoomSlider(device: device) { factor in updateUI(...) },UI 通过 closure 同步即可
- 为什么值得做:
关联 Session
- Capture cinematic video in your app — Cinematic Video API 让 app 录制带浅景深效果的电影级视频
- Enhance your app with machine-learning-based video effects — 帧率转换、超分辨率、降噪等基于机器学习的视频效果
- Build a great Lock Screen camera capture experience — 配置 Lock Screen capture extension,让 Camera Control 锁屏可启动你的 app
- Bring your camera app to Mac with Continuity Camera — Mac 上的相机延伸场景,与多设备捕获策略相关
评论
GitHub Issues · utterances