LoginSignup
5
3

More than 1 year has passed since last update.

[SwiftUI] `overlay` を使って配置に優先度を付ける

Posted at

前提

SwiftUIでは、何も考えずにViewを配置すると基本的にセンタリングされて描画されます。
そのため、

HStack {
    Text("Foo")
}

HStack {
    Spacer()
    Text("Foo")
    Spacer()
}

は同じ見た目になります。
※厳密には異なっておりbackgroundを指定すると分かるのですがここでは割愛します。

では、左右に別のViewを置くとどうなるでしょうか?

色々なパターンを試した結果がこちらです。
※分かりやすいようにbackgroundを付けています
image.png

本題

上の画像から分かる通り中心に置いたつもりのViewが左右(VStackであれば上下)のViewの大きさに応じてズレてしまうことがあります。
しかし、デザイン要件でどうしても中心に置きたいViewがあるとします。

Interface Builder(xibやstoryboard)であれば、中心に置きたいViewにCenter X(or Y)のConstraintを貼ってそれを基準に他のConstraintを貼っていけば実現できます。簡単ですね。
image.png

一方、SwiftUIではH/VStackの中で基準となるViewを決めるといったことができません(観測した限りでは)
(alignmentGuideも検討してみましたが無視されました...)

この時、 overlay modifierを使うことで解決できる場合があります。

最初の画像に適用した結果がこちら。

image.png

※スクショを撮った際のスクリーンに収まりきらなかったのでこちらにソースコードのリンクを貼っておきます
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が描画されることになります。

image.png

これで、期待していた中央固定ができるようになりました。

ちなみに、縦でも同じことができるように見えますが単純にHStackVStackに置き換えただけでは何故か上手くいきません。

image.png

この場合、Spacer()の代わりにColor.clearを使うと上手く行きます。
image.png

ちなみに、お気づきかもしれませんが横でSpacer()の代わりにColor.clearを使うと全体の縦幅が変わります。
image.png

最後に

ここで紹介した方法は必ずしも上手くいくとは限らず実際にはここから更に試行錯誤が必要ですが、そのための取っ掛かりにでもなれば幸いです。

Ref

  1. SwiftUIのソースを見たわけではないので挙動からの推測です。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3