SwiftUI の ViewThatFits で利用可能なスペースに適応する
iOS 16 で導入された ViewThatFits は、複数のレイアウト候補を用意しておき、利用可能なスペースに収まる最初のビューを自動的に選択するコンテナです。画面サイズや Dynamic Type の変化に応じてレイアウトを切り替えたいとき、GeometryReader より宣言的に書けるのが利点になります。
基本の使い方
ViewThatFits はクロージャ内に列挙したビューを上から順にチェックし、親のスペースに収まる最初のビューを表示します。
ViewThatFits {
// 第1候補: 横並び
HStack(spacing: 12) {
Image(systemName: "star.fill")
Text("お気に入りに追加")
Spacer()
Button("追加") { }
.buttonStyle(.borderedProminent)
}
// 第2候補: 縦並び
VStack(spacing: 8) {
HStack {
Image(systemName: "star.fill")
Text("お気に入りに追加")
}
Button("追加") { }
.buttonStyle(.borderedProminent)
}
// 第3候補: アイコンのみ
Button { } label: {
Image(systemName: "star.fill")
}
.buttonStyle(.borderedProminent)
}画面幅が十分なら横並びレイアウト、狭くなると縦並び、さらに狭いとアイコンだけのボタンに自動で切り替わります。候補は上から順に評価されるため、最も理想的なレイアウトを最初に書くのがポイントです。
軸を指定する
ViewThatFits は水平と垂直の両方でフィットを判定しますが、in パラメータで判定軸を限定できます。
ViewThatFits(in: .horizontal) {
Text("これは長いテキストで横幅を多く使います")
Text("短いテキスト")
Text("短")
}.horizontal を指定すると、水平方向のスペースだけで判定が行われます。垂直方向がはみ出してもフィットしたと見なされるため、幅だけに応じた切り替えが実現できます。
水平・垂直の両方でスペースに収まるかを判定する。どちらかの軸ではみ出すと次の候補に進む
水平方向のみで判定する。高さは無視されるため、幅だけに応じたレイアウト切り替えが可能
Dynamic Type への対応
ViewThatFits が特に威力を発揮するのは、Dynamic Type(文字サイズの変更)への対応です。ユーザーがシステム設定でフォントサイズを大きくすると、通常のレイアウトに収まらなくなることがあります。
ViewThatFits {
HStack {
Label("設定", systemImage: "gear")
Spacer()
Text("一般")
.foregroundColor(.secondary)
Image(systemName: "chevron.right")
.foregroundColor(.secondary)
}
VStack(alignment: .leading) {
Label("設定", systemImage: "gear")
HStack {
Text("一般")
.foregroundColor(.secondary)
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(.secondary)
}
}
}標準的なフォントサイズでは横並び、アクセシビリティサイズでは自動的に縦並びに切り替わります。GeometryReader でブレークポイントを計算するよりもずっと簡潔に書けるのが魅力です。
GeometryReader との使い分け
ViewThatFits と GeometryReader はどちらもレスポンシブレイアウトに使えますが、適材適所があります。
ViewThatFits は「複数の完成形をあらかじめ用意し、スペースに応じて切り替える」パターンに向いています。一方、GeometryReader は「正確な数値を取得して計算に使う」パターンに向いています。
// ViewThatFits が適しているケース
// → レイアウトのバリエーションが数パターンに限られる
ViewThatFits {
WideLayout()
NarrowLayout()
MinimalLayout()
}
// GeometryReader が適しているケース
// → サイズに応じて連続的に値を変えたい
GeometryReader { proxy in
let columns = Int(proxy.size.width / 120)
// columns に応じた処理
}明確なバリエーションがあるなら ViewThatFits、連続的なサイズ計算が必要なら GeometryReader を選びましょう。両方を組み合わせることも可能で、GeometryReader で大まかなサイズ区分を判定し、その中で ViewThatFits を使ってさらに細かい調整を行うといったアプローチも取れます。