Highlight
iOS 26 では、H2 チップを搭載した AirPods のステム部分をタップすることでリモートからカメラ撮影をトリガーできるようになりました。すでに AVCaptureEventInteraction を導入済みの app であれば、コードを 1 行も変更することなくこの機能を自動的に獲得できます。
主要内容
サードパーティ製のカメラ app には、長年気まずい体験の断絶がありました。ユーザーが純正カメラで慣れ親しんだ「音量キーで撮影」「長押しで録画」「Camera Control でフォーカス調整」といった操作が、サードパーティ製 app ではすべて効かないのです。画面上の仮想シャッターボタンは、iPhone 側面の物理ボタンほど自然な操作感には到底及びません。さらに困るのが自撮りシーンで、スマートフォンを離れた場所に設置すると撮影をトリガーする手段がまったくありません。
WWDC25 の本セッションでは、AVKit チームの Vitaliy 氏とカメラ体験チームの Nick 氏が共同で 2 つの API を解説します。1 つ目の AVCaptureEventInteraction は、音量キー・Action ボタン・Camera Control の物理的な押下イベントを app 内の capture コールバックに変換するもので、わずか 6 行の SwiftUI コードでサードパーティ製 app に純正カメラ同等の物理ボタン体験をもたらします。2 つ目の AVCaptureControl は、iPhone 16 の Camera Control をプログラマブルなハードウェアインターフェースとして公開し、連続スライダー・離散スライダー・Picker の 3 種類のコントロールに対応します。iOS 26 ではこの上に新たな能力が加わりました。H2 チップを搭載した AirPods のステムをタップすることでメインの capture イベントをリモートからトリガーでき、すでに AVCaptureEventInteraction を導入している app はコード変更なしでこの機能をそのまま使えます。
詳細
AVCaptureEventInteraction:物理ボタンを capture にマッピングする
物理ボタンは primary と secondary の 2 種類に分かれます(03:37)。音量ダウンキー・Action ボタン・Camera Control が primary action をトリガーし、音量アップキーが secondary action をトリガーします。同じボタンが両方の handler を同時にトリガーすることはありません。secondary handler はオプションで、指定しなかった場合は音量アップキーも primary に流れます。
押下のたびに発行されるイベント通知には、3 つの phase が含まれます(03:12)。
began:押下した瞬間。カメラオブジェクトのウォームアップに適していますcancelled:app がバックグラウンドに移行した、あるいは capture が利用不能になったとき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に付与し、カメラ view がアクティブなときだけイベントが消費されるようにしますevent.phase == .endedのときにのみcapturePhoto()を呼び、beganフェーズで早すぎるトリガーが起きないようにします- 画面上の
Buttonはそのまま残します。物理ボタンと仮想ボタンが同じcamera.capturePhotoメソッドを共有する形です
注意点として、システムは「カメラを使用中」の app にのみ capture event を送ります(04:22)。app がバックグラウンドにあったり、アクティブな AVCaptureSession がなかったりすると、ボタン押下はシステムのデフォルト動作(音量調整、カメラ起動)にフォールバックします。一度この API を導入したら、受け取ったイベントすべてに必ず応答してください。応答しないとボタンが反応しない状態になり、体験を著しく損ねます。
iOS 26:AirPods のステムによるリモート capture
H2 チップを搭載した AirPods は、iOS 26 でリモートカメラ capture 能力を獲得しました(06:38)。ユーザーは「設定 → Remote Camera Capture」で「Press once」または「Press and hold」を選択しておくだけで、AVCaptureEventInteraction を導入したカメラ app であればどれでも、AirPod のステムをタップすることでメイン capture をトリガーできます。この機能は開発者にとって完全に透過的で、API を導入済みの app は自動的に対応されます。
撮影時にユーザーが画面を見ていない可能性があるため、オーディオフィードバックが鍵になります。Apple はこのためにデフォルトのシャッター音を追加し、app 側でカスタマイズや無効化を行うための新 API も提供しました(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 経由のイベントはすべてfalseを返しますevent.play(.cameraShutter)でシステム標準のシャッター音を再生します。app の bundle に同梱した音声ファイルを再生することも可能です- たとえデフォルト音を切ったとしても、AirPod 経由のトリガー経路では何らかのフィードバック音を必ず再生してください。ユーザーは画面を見ておらず、聴覚情報だけが頼りです
AVCaptureControl:Camera Control をプログラマブルにする
Camera Control は iPhone 16 側面の物理コントロールで、タップで撮影、軽押しでプレビュー呼び出し、スライドで調整、ダブルタップで設定切り替えに対応します(11:02)。AVFoundation はこれを AVCaptureControl として抽象化しており、大きく 2 種類に分かれます。
- Slider(数値):連続型は範囲内で任意の値を取ります(zoom factor など)。離散型は指定したステップだけを取ります(露出補正、±2 EV の 1/3 ステップなど)
- Picker(リスト):各 index が名前付きの状態に対応します(Flash の On/Off、Photographic Style の名前など)
システムには 2 つの定義済みサブクラス AVCaptureSystemZoomSlider と AVCaptureSystemExposureBiasSlider があり、挙動は純正カメラに揃えられています。さらに 2 つの汎用サブクラス(AVCaptureSlider、AVCaptureIndexPicker)が用意されており、独自のコントロールを定義する際に使います。
system zoom slider を 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)は 1 行で生成できます。システムコントロールはdevice参照を直接受け取り、自動的にvideoZoomFactorを駆動するため、app 側で手動設定する必要はありませんcanAddControlは必須のチェックです。たとえば device にすでに zoom control が紐づいている場合、2 つ目を追加しようとすると失敗します- 全体は
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 に置くことです。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 アイコンが必要です。複数のコントロールが並ぶ状況では、ユーザーがどれの Picker かを識別する手がかりはアイコンになりますpicker.isEnabled = device.canPerformReactionEffects:コントロールが利用不能な場合は省略するのではなく disabled 状態にすること。省略するとシステムが別のコントロールにフォールバックしてしまい、ユーザーを混乱させます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がアクティブであることを確認します
-
何をするか:シャッター音をカスタマイズし、AirPods 経由のシナリオで差別化されたフィードバックを返す
- なぜ価値があるか:AirPods で撮影するユーザーは画面を見ていないため、効果音が唯一の確認シグナルになります。デフォルトの効果音は app の世界観(プロ向けカメラとフィルター app では必要な音色が異なります)と必ずしも合致しません
- どう始めるか:
defaultSoundDisabled: trueでデフォルト音を切り、closure 内でevent.shouldPlaySoundを判定して再生有無を決めます。shouldPlaySoundは AirPod 経路のときだけ true になるため、他の経路で音が二重に鳴る心配はありません
-
何をするか:iPhone 16 で Photographic Style / フィルター用の Picker を追加し、Camera Control に結びつける
- なぜ価値があるか:ユーザーはすでに Camera Control 上の「軽押し 2 回でコントロール切り替え → スライドで選択 → 軽押しで確定」という筋肉記憶を獲得しています。カスタム Picker はこの一連のインタラクションをそのまま再利用できます
- どう始めるか:
AVCaptureIndexPickerでフィルターのリストをラップし、setActionQueue(sessionQueue)で切り替え動作を session queue に乗せて device 設定を同期的に処理します。利用不能なフィルターは省略せずisEnabled = falseにすることを忘れないでください
-
何をするか:zoom や露出補正を自前実装からシステム定義のコントロールに置き換える
- なぜ価値があるか:
AVCaptureSystemZoomSliderは 1 行で生成でき、ステップ・反発・HUD 表示まで含めて挙動が純正カメラと完全に揃います。これだけの一貫性を自前で実装するのは困難です - どう始めるか:自作の zoom コントロール実装を削除し、
AVCaptureSystemZoomSlider(device: device) { factor in updateUI(...) }に置き換えます。UI 側は closure 経由で同期するだけで済みます
- なぜ価値があるか:
関連セッション
- 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 上でのカメラ拡張シナリオ。マルチデバイス capture 戦略と関連
コメント
GitHub Issues · utterances