
iPhone Duoの折り目やカメラの位置を取得する
はじめに
以前、iOS 27.1 SDKで追加されたiPhone Duo向けのAPIをまとめました。
今回はその中からreservedRegionsを深掘りします。
検証環境
- Xcode 27.2
- iOS 27.2 iPhone Duo シミュレーター
reservedRegionsとは
GeometryProxyのreservedRegionsで、折り目やカメラなど、画面を遮るものが占める領域を取得できる。
func reservedRegions(
kind: ReservedRegion.Kind,
options: ReservedRegion.QueryOptions = [],
layoutDirectionBehavior: LayoutDirectionBehavior = .mirrors
) -> [ReservedRegion]
返ってくるReservedRegionのframeは、取得したViewの座標。margins(周りの余白)も最初から含まれている。
kind
型はReservedRegion.Kind
取得したい領域の種類を指定する。値は2つ。
.occlusion: カメラやDynamic Islandなど、何かに覆われる場所.division: 折り目。半開きのときだけ有効で、全開のときは無効になり、デフォルトでは返ってこない。ドキュメントでは閉じているときも有効とされているが、シミュレーターでは閉じると.includeInactive(後述)を付けても返ってこなかった
iPhone Duoの場合、外側のカメラは常に有効、内側のカメラは使っている間だけ有効になる。
取得できた領域に色をつけて可視化してみた。
- 赤:
.occlusion - 青:
.division
| 全開 | 半開 |
|---|---|
![]() |
![]() |
options
型はReservedRegion.QueryOptions
指定できるのは.includeInactiveのみ。
OptionSetなので、単体でも[.includeInactive]のように配列リテラルでも指定できる。推測になるが、今後オプションが増える可能性を考慮していると思う。
デフォルト([])では有効な領域だけが返ってくるが、.includeInactiveを渡すと無効な領域も返ってくる。有効かどうかはisActiveで判定できる。
内側のカメラや折り目のように、状態によって有効になる領域にあらかじめスペースを空けておけば、レイアウトがガタつかない。
.includeInactiveを渡して領域を取得し表示してみた。
指定なしの状態に比べ、有効でない領域も取得できていることが分かる。
- 赤:
.occlusion - 青:
.division
| 全開 | 半開 |
|---|---|
![]() |
![]() |
layoutDirectionBehavior
アラビア語など右から左に書く言語(RTL)では、SwiftUIは座標を右端から測る。layoutDirectionBehaviorは、frameの数字をどちらの端から測って返すかを決める。領域の場所そのものは変わらない。
.mirrors(デフォルト): RTLのときは右端から測る(SwiftUIのレイアウトと同じ).fixed: 反転しない(RTLでもLTRと同じく左端から測る)
iPhone Duoのシミュレーターで、右端にあるカメラ(.occlusion)のframeを取ってみた。RTLは.environment(\.layoutDirection, .rightToLeft)で再現している。画面の幅は951。
.mirrors |
.fixed |
|
|---|---|---|
| LTR | 867.0〜951.0 | 867.0〜951.0 |
| RTL | 0.0〜84.0 | 867.0〜951.0 |
RTLの.mirrorsは右端から測った値になる。SwiftUIもRTLでは右端から測って配置するので、この値をそのまま使えばカメラの位置に揃う。普通はデフォルトのままでいい。
どちらで測るかは、端末の言語ではなく、取得したViewのlayoutDirectionで決まる。.environment(\.layoutDirection, .leftToRight)を付けたViewでは、.mirrorsも左端から測った値になった。
.fixedは、レイアウト方向に合わせて反転しない配置をするときに使う(公式ドキュメントより)。ただ、普通のSwiftUIのレイアウトはRTLで自動的に反転するので、.fixedが必要になる場面は自分ではよくわからなかった。
使ってみる
.divisionのframeの縦横を比べると、折り目の向きがわかる。これを使って2つのViewを折り目の左右(上下)に分けてみた。
GeometryReader { proxy in
if let fold = proxy.reservedRegions(kind: .division).first?.frame {
if fold.height > fold.width { // 縦の折り目
HStack(spacing: fold.width) {
LeftView().frame(width: fold.minX)
RightView().frame(width: proxy.size.width - fold.maxX)
}
} else { // 横の折り目
VStack(spacing: fold.height) {
TopView().frame(height: fold.minY)
BottomView().frame(height: proxy.size.height - fold.maxY)
}
}
} else { // 折り目がない(全開)
SingleView()
}
}
| 本のように半開 | ノートPCのように半開 | 全開 |
|---|---|---|
![]() |
![]() |
![]() |
ただ、2つのViewを折り目の両側に分けたいだけなら、自分で領域を読まずにArrangementViewを使うよう公式ドキュメントで推奨されている。
まとめ
frameは取得したViewの座標.divisionは半開きのときだけ有効。全開のときは.includeInactiveで無効として取れるlayoutDirectionBehaviorはRTLのときのframeの測り方を決める。普通はデフォルトのままでいい
ドキュメントではシステム提供のViewが調整してくれるとされているので、出番は限られそう。
参考
















