【SwiftUI】Textにブランクが入ったときに意図せぬ高さに設定されてしまう場合の対応

SwiftUIで`Text`を使っていて、
ブランクが入った場合に想定していた値と異なる高さが設定されレイアウトが崩れるというケースがありました。
初歩的かもしれませんが、その事象の共有と対応について記載しようと思います。

## 環境
```
Xcode：14.1
シミュレーター：iPhone14 Pro（iOS:16.1）
```

## 事象
`Text`を縦方向に並べるケースを考えます

```Swift
struct SwiftUIView: View {

    var body: some View {
        HStack(alignment: .top) {
            VStack(alignment: .leading, spacing: 0) {
                Text("Text")
                    .foregroundColor(Color.white)
                    .background(Color.blue)
                Text("Text")
                    .foregroundColor(Color.white)
                    .background(Color.red)
                Text("Text")
                    .foregroundColor(Color.white)
                    .background(Color.green)
            }
            VStack(alignment: .leading, spacing: 0) {
                Text("Text")
                    .foregroundColor(Color.white)
                    .background(Color.blue)
                Text("")
                    .foregroundColor(Color.white)
                    .background(Color.red)
                Text("Text")
                    .foregroundColor(Color.white)
                    .background(Color.green)
            }
        }
    }
}
```

右のVStackはそれぞれ任意の文字列が入る想定で、
わかりやすいように背景色を設定しています。
また、左には高さの比較用にVStackを加えています。

想定としては右のVStackは上から二番目の領域にブランクが入っていますが、
レイアウトとしては設定したフォントサイズ分の余白を期待すると思います。
が、表示としてはスクショのようになり、左の基準に対して高さがずれます。

<img src="https://user-images.githubusercontent.com/42941654/211138915-515dd012-d74c-4af6-b150-69a6b3de344c.png" width=50%>

フォントやフォントサイズを変えてViewHierarchyで確認してみたところ、
`Text`にブランクが入った場合はデフォルトで14pt分の余白が設定されるようでした。

![スクリーンショット 2023-01-07 16 27 06](https://user-images.githubusercontent.com/42941654/211139350-18dd3f1c-9245-480d-88d5-329d1b801fb0.png)

なぜ14pt分空いてしまうのか私が調べた範囲だとわからなかったのですが、
とにかくブランクでも一行分の空白をあけておくために以下の対応を行いました

## 対応方法

めちゃくちゃ単純ですが、`.frame(minHeight:)`を設定します

```Swift
struct SwiftUIView: View {

    var body: some View {
        HStack(alignment: .top) {
            VStack(alignment: .leading, spacing: 0) {
                Text("Text")
                    .frame(minHeight: 20)
                    .foregroundColor(Color.white)
                    .background(Color.blue)
                Text("Text")
                    .frame(minHeight: 20)
                    .foregroundColor(Color.white)
                    .background(Color.red)
                Text("Text")
                    .frame(minHeight: 20)
                    .foregroundColor(Color.white)
                    .background(Color.green)
            }
            VStack(alignment: .leading, spacing: 0) {
                Text("Text")
                    .frame(minHeight: 20)
                    .foregroundColor(Color.white)
                    .background(Color.blue)
                Text("")
                    .frame(minHeight: 20)
                    .foregroundColor(Color.white)
                    .background(Color.red)
                Text("Text")
                    .frame(minHeight: 20)
                    .foregroundColor(Color.white)
                    .background(Color.green)
            }
        }
    }
}
```

<img src="https://user-images.githubusercontent.com/42941654/211139554-f2d329ba-2ca4-4571-828f-9dcb450981f5.png" width=50%>

最小のheightが指定されてるので、ブランクになっても意図したサイズを保ってくれます。

## 直接heightを指定しない理由

ここまでが本件の解決策ですが、ここでわざわざminHeightを指定して、heightを使わない理由を記載します。

私も最初はheightを直接指定していましたが、複数行のケースも考えるとminHeight指定がベターだなと考えました。

赤の領域に入るテキストが2行を許容する場合を考えます。
背景の幅をそろえるため、テキストのframeの幅も指定しています。
また、ここ以降では左の基準として使っていたVStackは削除しています。


```Swift
struct SwiftUIView: View {
    
    let width: CGFloat = UIScreen.main.bounds.width
    
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            Text("Title")
                .frame(width: width, height: 20)
                .foregroundColor(Color.white)
                .background(Color.blue)
            Text("長いテキスト長いテキスト長いテキスト長いテキスト長いテキスト")
                .lineLimit(2)
                .frame(width: width, height: 20)
                .foregroundColor(Color.white)
                .background(Color.red)
            Text("Title")
                .frame(width: width, height: 20)
                .foregroundColor(Color.white)
                .background(Color.green)
        }
    }
}
```

この場合、heightを直接指定していると2行許容が反映されません。

<img src="https://user-images.githubusercontent.com/42941654/211140271-151f78ea-5594-471d-a324-c5bf9ffd75b1.png" width=50%>

frameを直接指定するのは可変の文字列に対しては難しいため、やはりminHeightで指定するのがよさそうです。

minHeightを使用した場合↓

```Swift
struct SwiftUIView: View {
    
    let width: CGFloat = UIScreen.main.bounds.width
    
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            Text("Title")
                .frame(minWidth: width, minHeight: 20)
                .foregroundColor(Color.white)
                .background(Color.blue)
            Text("長いテキスト長いテキスト長いテキスト長いテキスト長いテキスト")
                .lineLimit(2)
                .frame(minWidth: width, minHeight: 20)
                .foregroundColor(Color.white)
                .background(Color.red)
            Text("Title")
                .frame(minWidth: width, minHeight: 20)
                .foregroundColor(Color.white)
                .background(Color.green)
        }
    }
}
```

<img src="https://user-images.githubusercontent.com/42941654/211140363-ca65befe-9114-4c0b-96a6-150e185a94c4.png" width=50%>


## 終わりに
xibやstoryboardでレイアウトを組んでいたときは、制約の付け方が柔軟だったのでよしなにレイアウトを組めたけど、SwiftUIでは厳密に理解して組まないと結構些細なことで崩れてしまうので難しいんですよねぇ。。