前提
SwiftUIでは、何も考えずにViewを配置すると基本的にセンタリングされて描画されます。
そのため、
HStack {
Text("Foo")
}
と
HStack {
Spacer()
Text("Foo")
Spacer()
}
は同じ見た目になります。
※厳密には異なっておりbackgroundを指定すると分かるのですがここでは割愛します。
では、左右に別のViewを置くとどうなるでしょうか?
色々なパターンを試した結果がこちらです。
※分かりやすいようにbackground
を付けています
本題
上の画像から分かる通り中心に置いたつもりのViewが左右(VStackであれば上下)のViewの大きさに応じてズレてしまうことがあります。
しかし、デザイン要件でどうしても中心に置きたいViewがあるとします。
Interface Builder(xibやstoryboard)であれば、中心に置きたいViewにCenter X(or Y)
のConstraintを貼ってそれを基準に他のConstraintを貼っていけば実現できます。簡単ですね。
一方、SwiftUIではH/VStack
の中で基準となるViewを決めるといったことができません(観測した限りでは)
(alignmentGuide
も検討してみましたが無視されました...)
この時、 overlay
modifierを使うことで解決できる場合があります。
最初の画像に適用した結果がこちら。
※スクショを撮った際のスクリーンに収まりきらなかったのでこちらにソースコードのリンクを貼っておきます
https://github.com/417-72KI/SwiftUISamplePackage/blob/center-sample/Sources/SwiftUISamplePackage/Center/CenterWithOverlaySampleView.swift
解説
一番シンプルなパターンを考えます。
HStack {
Text("FooBar") // - ①
Spacer() // - ②
Text("Baz") // - ③
Spacer() // - ④
Text("Qux") // - ⑤
}
この時キャプチャを撮ると②と④のwidth
が同じになることが分かります。
つまり①・③・⑤のwidth
を計算した後に残った分をSpacer
の数(②・④の2つ)で分割していると考えられます1。
①と⑤のサイズが同じであれば③が中央に来ますが、そうでない場合にズレが発生することになります。
ここで必要なのは③の左右が同じwidthに固定されることです。
そこで、以下のようにします。
HStack {
Spacer() // - ②
.overlay(
HStack {
Text("FooBar") // - ①
Spacer()
}
)
Text("Baz") // - ③
Spacer() // - ④
.overlay(
HStack {
Spacer()
Text("Qux") // - ⑤
}
)
}
※違いが分かりやすいように丸数字は先と同じになるようにしています
考えられる処理順
初めに②、③、④からそれぞれのwidth
を決定します。
状況としては以下と同じです。
HStack {
Spacer() // - ②
Text("Baz") // - ③
Spacer() // - ④
}
overlay
は元のviewのサイズに合わせて展開されるため、
②の領域に①を含むHStack
が、④の領域に⑤を含むHStack
が描画されることになります。
これで、期待していた中央固定ができるようになりました。
ちなみに、縦でも同じことができるように見えますが単純にHStack
をVStack
に置き換えただけでは何故か上手くいきません。
この場合、Spacer()
の代わりにColor.clear
を使うと上手く行きます。
ちなみに、お気づきかもしれませんが横でSpacer()
の代わりにColor.clear
を使うと全体の縦幅が変わります。
最後に
ここで紹介した方法は必ずしも上手くいくとは限らず実際にはここから更に試行錯誤が必要ですが、そのための取っ掛かりにでもなれば幸いです。
Ref
-
SwiftUIのソースを見たわけではないので挙動からの推測です。 ↩