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

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

```swift
HStack {
    Text("Foo")
}
```
と
```swift
HStack {
    Spacer()
    Text("Foo")
    Spacer()
}
```
は同じ見た目になります。
※厳密には異なっておりbackgroundを指定すると分かるのですがここでは割愛します。

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

色々なパターンを試した結果がこちらです。
※分かりやすいように`background`を付けています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135304/87b504a1-ffcd-193a-4cc3-5b1d71976b5c.png)

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

Interface Builder(xibやstoryboard)であれば、中心に置きたいViewに`Center X(or Y)`のConstraintを貼ってそれを基準に他のConstraintを貼っていけば実現できます。簡単ですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135304/8a602816-8ab6-b623-9204-ac0a838ecd64.png)

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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135304/d765b089-fc87-7c42-82b9-60c4a107a88f.png)

※スクショを撮った際のスクリーンに収まりきらなかったのでこちらにソースコードのリンクを貼っておきます
https://github.com/417-72KI/SwiftUISamplePackage/blob/center-sample/Sources/SwiftUISamplePackage/Center/CenterWithOverlaySampleView.swift

## 解説

一番シンプルなパターンを考えます。

```swift
HStack {
    Text("FooBar") // - ①
    Spacer()       // - ②
    Text("Baz")    // - ③
    Spacer()       // - ④
    Text("Qux")    // - ⑤
}
```

この時キャプチャを撮ると②と④の`width`が同じになることが分かります。
つまり①・③・⑤の`width`を計算した後に残った分を`Spacer`の数(②・④の2つ)で分割していると考えられます[^1]。
[^1]:SwiftUIのソースを見たわけではないので挙動からの推測です。

①と⑤のサイズが同じであれば③が中央に来ますが、そうでない場合にズレが発生することになります。

ここで必要なのは③の左右が同じwidthに固定されることです。
そこで、以下のようにします。

```swift
HStack {
    Spacer()                   // - ②
        .overlay(
            HStack {
                Text("FooBar") // - ①
                Spacer()
            }
        )
    Text("Baz")                // - ③
    Spacer()                   // - ④
        .overlay(
            HStack {
                Spacer()
                Text("Qux")    // - ⑤
            }
        )
}
```

※違いが分かりやすいように丸数字は先と同じになるようにしています

### 考えられる処理順
初めに②、③、④からそれぞれの`width`を決定します。
状況としては以下と同じです。

```swift
HStack {
    Spacer()    // - ②
    Text("Baz") // - ③
    Spacer()    // - ④
}
```

`overlay` は元のviewのサイズに合わせて展開されるため、
②の領域に①を含む`HStack`が、④の領域に⑤を含む`HStack`が描画されることになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135304/0b6c1c88-d9a4-d674-47ac-3c23c2e5395f.png)

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135304/aef87642-8517-16dc-812c-39228a86250e.png)

この場合、`Spacer()`の代わりに`Color.clear`を使うと上手く行きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135304/d76a022f-af7b-bd86-c814-52debc73699f.png)

ちなみに、お気づきかもしれませんが横で`Spacer()`の代わりに`Color.clear`を使うと全体の縦幅が変わります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135304/ef58030b-42ea-fcae-74d5-199f5b6d1582.png)


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

# Ref
https://stackoverflow.com/questions/64266470/swiftui-how-to-vertically-center-a-view-inside-a-vstack