iPhone Duoの発表後、同僚から最初に受けた質問は「折り曲げ角度って読めるの?」でした。読めます。Appleはヒンジの離散的なステータスと連続的な角度の両方を、SwiftUIとUIKitの双方で提供しています。ただし、このAPIを紹介する同じTech Talkの中で、厳しい制約も語られています。ヒンジはエフェクトとインタラクションのためのものであって、layoutのためのものではない、と。この違いは本質的で、新APIの後半戦はすべてこの上に成り立っています。順番に見ていきましょう:hinge API、reserved regions、arrangements、そしてカメラ——Objective-Cから使えるものと、Swiftが必要になるものを分けながら。
シリーズ第1回と同じ注意書きを繰り返しておきます。シグネチャはTech Talks 111463〜111465からの引用で、Xcode 27.1はまだbetaです。本番投入前にSDKヘッダと照合してください。
onHingeChange:3つのステータスとライブな角度#
SwiftUIモディファイアonHingeChangeは、直前と現在のコンテキストを引数にクロージャを呼び出します。Appleの定番サンプルはギターアプリで、折り曲げ角度がトレモロアームとして機能します。
struct InstrumentView: View {
/// 0 — no bend, 1 — maximum
@State private var pitchBend: Double = 0
var body: some View {
GuitarView(pitchBend: pitchBend)
.onHingeChange { _, context in
if let hinge = context.hinge, hinge.status == .partiallyOpen {
pitchBend = calculatePitchBend(angle: hinge.angle)
} else {
pitchBend = 0
}
}
}
}ここには見落としやすい必須要素が2つあります。
context.hingeはOptionalです。nilはデバイスにヒンジがないことを意味します——同じバイナリが通常のiPhoneでも動くからです。guardなしのコードは、世界の大多数のデバイスでクラッシュします。- リセットを行う
else分岐。 ステータスは.closed、.partiallyOpen、.fullyOpenの3つ。.closedのときだけエフェクトを解除する実装だと、デバイスを完全に開いて平らにした瞬間、エフェクトが最後の角度のまま「固着」します。
UIKitで同じことをするのがUIHingeInteractionです。UIInteractionファミリーの慣例どおりviewに追加する形式で、つまりObjective-Cから利用できます。
// API shape follows the UIInteraction family; verify the exact
// handler signature against the iOS 27.1 SDK headers
UIHingeInteraction *hinge = [[UIHingeInteraction alloc] init];
[self.effectView addInteraction:hinge];ここにないものが重要です:通知です。ヒンジ用のNSNotificationに言及した公式ソースはひとつもなく、観測手段はinteraction/モディファイアだけです。どこかでUIDeviceHingeDidChangeNotificationを見かけたら、それは捏造です。
なぜヒンジをlayoutに使ってはいけないのか#
誘惑は明らかです。角度を読み、それに応じてインターフェースを組み替えたくなります。Appleはこれをやるなと直接的に言っています。角度は連続的なセンサーストリームであり、それに紐づいたlayoutは折り曲げ動作の間ずっとガタつき、ヒンジのないすべてのデバイスで壊れます。構造的な判断のためには、別のAPIが2つ用意されています。
Reserved regions(iOS 27.1)——ハードウェアに「占有された」viewの領域です。種類は2つ。.divisionは折り目で、領域を分割します(デバイスが部分的に折れている間だけアクティブで、平らな状態では非アクティブかつ幅ゼロ)。.occlusionはカメラで、領域を覆い隠します。
// SwiftUI — from GeometryProxy
let folds = proxy.reservedRegions(kind: .division)
let cameras = proxy.reservedRegions(kind: .occlusion)
// For stable decisions: account for the fold even while inactive
let allFolds = proxy.reservedRegions(kind: .division, options: .includeInactive)// Objective-C: same method on UIView, regions are UIViewReservedRegion
NSArray<UIViewReservedRegion *> *folds =
[self.view reservedRegionsWithKind:UIViewReservedRegionKindDivision
options:0];
for (UIViewReservedRegion *region in folds) {
// region.frame is the fold rectangle in view coordinates
}.includeInactiveオプションは、気づきにくい問題を解決します。折り目のregionが現れたり消えたりするたびにグリッドを組み直していると、ヒンジが動くたびにインターフェースが「シャッフル」されてしまいます。安定した解決策は、たとえば「ヒンジを持つデバイスでは常に偶数カラムにする」といった判断です。
朗報もあります。システムコンテナはfold avoidanceを無料で備えています。NavigationSplitView、TabView、sheet、alert、メニュー、popoverは、インタラクティブな要素を折り目の湾曲から自動で遠ざけます。唯一の例外はスクロールするコンテンツです。記事やフィードは折り目の下を流れて構わず、動かす必要はありません。
ArrangementView:splitかoverlayか#
「ナビゲーションとコンテンツの中間」に位置するviewのペアのために、専用コンテナが登場しました。SwiftUIではArrangementView、UIKitではUIArrangementViewControllerです。
ArrangementView {
PlayerView()
} secondary: {
UpNextView()
}
.arrangementViewStyle(.split)UIArrangementViewController *arrangement = [[UIArrangementViewController alloc] init];
[arrangement setViewController:playerVC forPlacement:UIArrangementPlacementPrimary];
[arrangement setViewController:upNextVC forPlacement:UIArrangementPlacementSecondary];スタイルは2つで、意味論が異なります。Splitはboundsを分割し、何も覆いません——プレイヤーとトランスクリプトのようなmain/detail構成向けです。iPadの挙動に対する嬉しい違いとして、secondaryを閉じてもprimaryのviewは画面中央へ再センタリングされず、折り目のそばに留まります。Overlayはスタックで、折り曲げ時に横並びに展開されます——スクロールコンテンツの上に載るforegroundコントロール向けです。移行のヒューリスティクスは単純で、HStack/VStackだったものはsplit、ZStackだったものはoverlayです。
Tech Talkからの禁止事項も2つ。arrangementの中にNavigationSplitViewを入れないこと(このコンテナはナビゲーション基盤を提供しません)、そしてarrangementをListやScrollViewの中に入れないこと。折りたたみ可能なカラムが必要なら、それは今までどおりUISplitViewControllerの仕事で、arrangementの用途ではありません。
カメラ:position == .frontはもう「こちらを見ている」を意味しない#
DuoはiPhoneとして初めてフロントカメラを2基搭載します。外側(4K/120)と内側の画面下カメラ(1080p/60)です。どちらも正直にposition == .frontを報告しますが、2つのディスプレイは正反対の方向を向き得るため、「フロント」カメラが必ずしもユーザーを見ているとは限りません。この事実の上で、2026年以前に書かれたミラーリングのロジックはすべて壊れます。
簡単な道は仮想フロントカメラです。デバイスの開閉に合わせて内側モジュールと外側モジュールを自動で切り替えてくれるシステムデバイスで、ディスカバリーは従来どおり(position .frontを指定したAVCaptureDeviceDiscoverySession——Objective-Cからも動きます)。ただし得られるのは両カメラの能力の共通部分だけです:1080p、60 fps、depthなし。大半のアプリにはこれで十分で、モジュールの切り替えはあなたの仕事ではなくなります。
特定モジュールのフル性能が必要なら、新しいデバイスタイプ.builtInInnerUltraWideCameraと.builtInOuterUltraWideCameraがあります——ただしその場合、切り替えはあなたの責任です。そこで登場するのが目玉の新ツール、AVKitのAVCaptureDeviceDirectionCoordinatorです。
let coordinator = AVCaptureDeviceDirectionCoordinator(
view: previewView,
deviceTypes: [.builtInInnerUltraWideCamera, .builtInOuterUltraWideCamera]
) { descriptor in
// The descriptor is Sendable; build the AVCaptureDevice on the camera actor
Task { await cameraActor.switchTo(descriptor) }
}コーディネーターは特定のviewに紐づき、「今このディスプレイと同じ方向を向いているカメラはどれか」という問いに答えます。main actorに隔離されており、actor境界を越えて渡されるのはAVCaptureDeviceではなく、そのsendable表現であるAVCaptureDeviceDescriptorです。changeハンドラの責務は3つ:セッションの再構成、positionではなく方向に基づくミラーリングの再計算、そしてUIの更新です。
Tech Talk 111465からカメラ周りの小ネタをあと2つ。AVCaptureDeviceRotationCoordinatorへ移行したらisCameraSensorOrientationCompensationEnabledを無効化してください——Duoのフロントカメラでは補正がデフォルトで有効なため、二重処理になります。またdynamicAspectRatioを使うと、内側画面のランドスケープに合わせて正方形センサーを活用できます。
2つ目のディスプレイにテレプロンプター#
カメラアプリにはもうひとつの可能性が開かれます。メインUIが内側ディスプレイでカメラセッションを動かしている間、外側ディスプレイに補助コンテンツを出せるのです——CameraCaptureAccessoryです。セッションで挙げられたシナリオは、動画撮影用のテレプロンプターや、写真を撮られる子どもに見せる楽しい何か、でした。
CameraView(model: model)
.sceneAccessory {
CameraCaptureAccessory(isEnabled: $model.isEnabled) {
TeleprompterView(model: model)
}
.onAvailabilityChange { model.isAvailable = $0 }
}可用性を管理するのはシステムです。したがってonAvailabilityChangeはオプションではなく義務です。ユーザーがデバイスを折りたたんだ瞬間、アクセサリはいつでも消え得ます。
まとめ#
新APIのロジックは一枚の絵にまとまります。ヒンジはエフェクトのためのセンサー、reserved regionsはlayoutのためのハードウェアの事実、arrangementsはviewペアの既製パターン、カメラの方向はpositionからは導けない独立した概念。Objective-CからはUIKit/AVFoundationの全表面が使えます。SwiftUI限定のAPI(onHingeChange、ArrangementView、sceneAccessory)をObjCプロジェクトで使うには、UIKitの対応物か薄いSwift層が必要になります——それがシリーズ第3回のテーマです。



