SwiftUIで複数のViewに被るViewを実装する

# これはなに

SwiftUIで複数のViewの上に、
複雑な表示を実装しようとしたときに調べた内容をメモします。

環境: Xcode11.5、iOS Simulator 13.xx

# Viewの上にViewを配置する

例えば以下のようなViewが二つ横並びの表示があったとします。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161845/2d31182d-5423-29a3-6406-702820d08ecd.png">

コードは以下の感じです。

```swift

struct Cell: View {
    var body: some View {
        VStack(alignment: .center) {
            Spacer()
            HStack(alignment: .center) {
                Spacer()
                Text("Cell")
                Spacer()
            }
            Spacer()
        }.border(Color.black)
    }
}

struct ZStackPlaygroundView: View {
    var body: some View {
        HStack {
            ForEach (0..<2) { _ in
                Cell()
            }
        }
    }
}
```

このView二つに被せて何かViewを上に載せたいとします。
例えば以下のような赤のラベルを重ねるなどです。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161845/5216df90-2e38-ab1b-cec7-fce8ea476ceb.png">

Cellに直接赤ラベルを配置しようとしてもうまくいきません。

```swift

struct HasRedLabelCell: View {
    var body: some View {
        VStack(alignment: .center) {
            Spacer()
            HStack(alignment: .center) {
                Spacer()
                // 赤ラベルをサイズを指定して追加
                Text("Cell").frame(width: 200).background(Color.red)
                Spacer()
            }
            Spacer()
        }.border(Color.black)
    }
}

struct ZStackPlaygroundView: View {
    var body: some View {
        ZStack(alignment: .top) {
            HStack {
                // ❗️赤ラベルをサイズの分だけHasRedLabelCellが大きくなるので二つのViewに重なる形で表示できない。
                HasRedLabelCell()
                Cell()
            }
        }
    }
}
```

なぜなら、**子Viewの大きさに合わせて親Viewの大きさが変わるからです。**
（親Viewからはみ出す方法はあるかもしれませんが、調べてないです）

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161845/bb2cef90-837b-8933-213f-0de77a74d85a.png">



なので親子関係にせずに、ZStackを使って同一階層にします。
ZStackを使うことによって、上にViewを配置することができます。

```swift

struct ZStackPlaygroundView: View {
    var body: some View {
        // ZStackを追加
        ZStack(alignment: .top) {
            HStack {
                ForEach (0..<2) { _ in
                    Cell()
                }
            }
            // ここのViewを二つのView(struct Cell)の上に被せる
            HStack {
                Spacer()
                Text("Label").colorInvert()
                Spacer()
            }
            .background(Color.red)
            .padding(.top, 250)
        }
    }
}
```

# 横幅の割合からViewの大きさを決める

親Viewの大きさに合わせてViewのサイズを変更したいとします。
このとき親Viewの大きさを動的に取得する方法にGeometryReaderを使う方法があります。

https://developer.apple.com/documentation/swiftui/geometryreader

GeometryReaderを使えば、親Viewのサイズなどのレイアウト情報を取得できます。

先程のZStackとGeometryReaderを組み合わせれば、親Viewに対して3/4の横幅にしてViewを被せるなど動的にサイズを変更できます。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161845/8b2d2174-9f3f-272f-4a72-7ea9ef87ea45.png">

実際のコードは以下の感じです。

```swift

struct ZStackPlaygroundView: View {
    var body: some View {
        ZStack(alignment: .top) {
            // GeometryReaderを使って、親Viewのサイズを取得
            GeometryReader { geometry in
                HStack {
                    ForEach (0..<2) { _ in
                        Cell()
                    }
                }
                HStack {
                    Spacer()
                    Text("Label").colorInvert()
                    Spacer()
                }
                // 親Viewのサイズの3/4を横幅として設定
                .frame(width: geometry.size.width * 0.75)
                .background(Color.red)
                .padding(.top, 250)
            }
        }
    }
}
```

さらに頑張れば段違いのちょっと複雑なViewも組むことができます。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161845/378b95b9-5935-25e8-b864-98de8b73f3dd.png">

実際のサンプルはGitHubにおいてあるので、気になる方は見てみてください。
https://github.com/rnishimu22001/SwiftUIPlayground/blob/master/SwiftUIPlayground/GeometoryPlaygroundView.swift

# まとめ

* Viewの上に何かViewをおきたければZStackを使う
* 親Viewのサイズを使って何かしたければGeomeotryReaderを使う

ZStackやGeometryReaderで別階層のViewのサイズと連動させるのは、
別階層同士が密結合になっていてあまりよくなさそうです。

もっと綺麗に連動できる方法があれば知りたいです