これは はてなエンジニア Advent Calendar 2025 の20日目の記事です。
SwiftUI での List や Form で使われる Section の Footer の中で、ある要素を下端に寄せたくなったことはありませんか?要素をVStackに入れてSpacerを挟めばできるでしょうと思ってやってみると、予想以上に複雑になります。
まず問題となるSectionの画面はこちらです。

お題はこの Bottom Aligned Footer というテキストを画面下端に寄せることです。
ただし、この末端の要素はあくまでスクロールビューの中の要素の一つとしたいので、List や Form のスクロールと連動し、表示領域が足りなければ画面から見えなくなるものとします。そのため、画面下端に固定するために overlay(alignment: .bottom) { ... } を使うことは適していません。overlayの場合、キーボードを表示した時などにテキストフィールドに要素が重なってしまうからです。
そのためFooter要素は VStack で構成し Text の間に Spacer を挟みます。先ほどのスクリーンショットは以下のようなコードになっています。
struct ContentView: View { var body: some View { Form { Section { TextField("TextField 1", text: Binding.constant("")) TextField("TextField 2", text: Binding.constant("")) TextField("TextField 3", text: Binding.constant("")) TextField("TextField 4", text: Binding.constant("")) } header: { Text("Header") } footer: { VStack(alignment: .leading) { Text("Footer") Spacer(minLength: 16) Text("Bottom Aligned Footer") } } } } }
ただ、これだけでは Spacer は広がりません。テキストを画面下端(SafeArea)に寄せるには Spacer に必要な距離を設定するか、 VStack 自体に高さを設定する必要があります。
Footerの1番上から画面下端までの高さを算出できれば良さそうですが、 Spacer には minLength が設定されていたことを考慮すると、 VStack 自体に必要な高さを求めることは、テキストを含めた子Viewそれぞれの高さも求める必要がでてきてしまい、やりたいことに対してやや複雑すぎる印象です。
そのため、末端の要素が画面下端に寄るように Spacer に必要な距離を設定するアプローチを紹介します。必要な距離は、FooterのbottomのY座標から、ルートのViewのbottomのY座標までの距離の差分です。
Viewの高さを算出する
まず Form 自体の高さを算出できるようにします。 View のサイズを取得できるモディファイアを作ります。これは SwiftUI ではよく知られた手法です。サブビューから親コンテナビューへの情報の送信には Preferences を使います。
extension View { func viewSize(onChange: @escaping (CGSize) -> Void) -> some View { overlay { GeometryReader { geometry in Color.clear.preference(key: ViewSizePreferenceKey.self, value: geometry.size) } } .onPreferenceChange(ViewSizePreferenceKey.self, perform: onChange) } } private struct ViewSizePreferenceKey: PreferenceKey { static var defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() } }
これで View の高さを取得することができます。今回のFormのケースの場合、高さをbottomのY座標として扱うことができます。
struct ContentView: View { @State var formBottomY: CGFloat = 0 var body: some View { Form { } .viewSize { formBottomY = $0.height } } }
要素の座標の取得する
サブビューの座標の取得にはまずサブビューの Anchor(レイアウト参照)を取得するので、 そのための PreferenceKey を用意します。
struct AnchorPointKey: PreferenceKey { static var defaultValue: Anchor<CGPoint>? static func reduce(value: inout Anchor<CGPoint>?, nextValue: () -> Anchor<CGPoint>?) { value = nextValue() } }
GeometryReader から得られるジオメトリ情報と、 anchorPreference(key:value:transform:)) から得られる Anchor を使って、Footer の bottom のY座標を取得することができます。
GeometryReader { geometry in
Form {
Section {
...
} header: {
...
} footer: {
VStack(alignment: .leading) {
...
}
.anchorPreference(key: AnchorPointKey.self, value: .bottom) { $0 }
}
.onPreferenceChange(AnchorPointKey.self) { anchor in
print(geometry[anchor].y)) // FooterのbottomのY座標が取得できる
}
}
}
差分を算出して Spacer に設定する
ここまで取得した Form のbottomのY座標とFooterのbottomのY座標を使うことで、 Spacer に必要な距離を算出できます。Anchorはスクロール毎に更新されるので、一度 Spacer の距離を設定したらそれを維持するようにしましょう。そして、 Form のサイズが切り替わったタイミングで Spacer の距離が再計算された値に更新されるように State は Optional で定義します。
struct ContentView: View { @State var spacerLength: CGFloat? @State var formBottomY: CGFloat = 0 { didSet { // 画面回転やキーボード表示でFormの高さが変わるのでSpacerを再計算する spacerLength = nil } } var body: some View { GeometryReader { geometry in Form { Section { TextField("TextField 1", text: Binding.constant("")) TextField("TextField 2", text: Binding.constant("")) TextField("TextField 3", text: Binding.constant("")) TextField("TextField 4", text: Binding.constant("")) } header: { Text("Header") } footer: { VStack(alignment: .leading) { Text("Footer") Spacer(minLength: spacerLength ?? 16) Text("Bottom Aligned Footer") } .anchorPreference(key: AnchorPointKey.self, value: .bottom) { $0 } } .onPreferenceChange(AnchorPointKey.self) { anchor in // FooterのbottomYからFormのbottomYまでの長さを計算して必要なSpacerの長さを算出する if let anchor, spacerLength == nil { let length = abs(formBottomY - geometry[anchor].y) spacerLength = max(16, length) } } } .viewSize { formBottomY = $0.height } } } }
これで Bottom Aligned Footer というテキストを画面下端に寄せることができました 🎉

List や Form は制約が多いので、今回のケースのように一見シンプルでも複雑なアプローチを強いられることがあります。実装したいUIが素朴に実装できるのかどうかまず試してみることが重要ですね...。
みなさんもこのお題にチャレンジしてみてください!もっとスマートな方法がきっとあるはずです。
明日は
id:yujiorama さんです!