paddingモディファイアって一体なんなの


## 子View同士の間隔はspacingを使うべきという話を読んだ
https://qiita.com/uhooi/items/d1d2f0f4916e053f5746

なるほど。  

つまり、「親Viewから提示される領域の中で親Viewが提示する範囲と子Viewとの距離を決める機能がpaddingで、子View同士の距離を決めるのは親Viewから与えられるspacingであるべき」、と理解した。
  
とすると、「paddingは子Viewが完全に制御していて、親Viewは領域確保（提示）に関与しないのか？」  
という疑問に陥った。

## paddingはいつ決まる？
paddingのイメージは、領域が決まった後に外側からの圧力で縮まる印象がある。paddingって意味からしても。
というわけで検証コード。

frameは指定しないで試す。

```Swift
struct ContentView: View {
    var body: some View {
        VStack {
            // 1
            HStack(spacing: 8) {
                RoundedRectangle(cornerRadius: 30)
                    .fill(.cyan)
                    .padding(20)
                    .background(.indigo)
                RoundedRectangle(cornerRadius: 30)
                    .fill(.blue)
                    .padding(20)
                    .background(.indigo)
                RoundedRectangle(cornerRadius: 30)
                    .fill(.green)
                    .padding(20)
                    .background(.indigo)
            }
            .padding(10)
            
            // 2
            HStack(spacing: 8) {
                RoundedRectangle(cornerRadius: 30)
                    .fill(.cyan)
                    .background(.indigo)
                    .layoutPriority(0)
                RoundedRectangle(cornerRadius: 30)
                    .fill(.blue)
                    .background(.indigo)
                    .layoutPriority(0)
                RoundedRectangle(cornerRadius: 30)
                    .fill(.green)
                    .padding(20)
                    .background(.indigo)
                    .layoutPriority(1)
            }
            .padding(10)
            
            // 3
            HStack(spacing: 8) {
                RoundedRectangle(cornerRadius: 30)
                    .fill(.cyan)
                    .padding(20)
                    .background(.indigo)
                    .layoutPriority(0)
                RoundedRectangle(cornerRadius: 30)
                    .fill(.blue)
                    .padding(20)
                    .background(.indigo)
                    .layoutPriority(0)
                RoundedRectangle(cornerRadius: 30)
                    .fill(.green)
                    .padding(20)
                    .background(.indigo)
                    .layoutPriority(1)
            }
            .padding(10)
        }
    }
}
```
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3432313936342f63363161396637642d636363652d643334302d653963662d6636643337303362313035302e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421964/d82b66de-8087-0b02-6d92-30effdaf6338.png)


1. Rectangle系は親Viewから与えられた領域一杯を使って描画しようとする性質がある。
つまり、同じlayoutPriorityであれば領域は等しく与えられ、大きさは同一になる

2. HStack内に与えられた子Viewのうち、１つだけlayoutPriorityが高い状態になるならば（優先されるならば）
spacingの領域を除いて他のHStackの領域は消失する  
ここまでは想定通り。

3. だが、2に加え各子Viewにpaddingをもたせる場合、子Viewは完全に押しつぶされずpaddingの領域を最低限確保をする。
その上で、cyanとblueのRoundedRectangleは「描画の領域が持てない状態」を作る

この結果から推測するに、親ViewがlayoutPriorityの高いViewに確保できる領域を提示する際、spacingとpaddingの領域を引いてから領域を提示しているように見える。
仮に「親Viewが子Viewが指定したpaddingの領域を確保した上で、子ViewのlayoutPriorityの高い順に領域を割り振る」と考える。
もし、paddingの領域を確保せずにlayoutPriorityの高い順に領域を決めるとすると、3は2と同じ結果になっていないといけないはずだったからだ。

試しに、paddingのサイズをViewが描画しきれないくらい大きな値にしたところ、layoutPriorityの高い低いに関わらず、HStackに属する全ての子ViewのRoundedRectangleが描画されなかった。

→ 親Viewはpaddingを持つ子Viewの領域確保にpaddingの値に関与していることがわかった。


## paddingをViewの持つ他のモディファイアが関与しない領域として考える

「paddingはpaddingが実行されるまでのViewにおいて、Viewの持つ他のモディファイアが関与しない領域の設定」と考える。
メソッドチェーンでモディファイアを繋げていくが、paddingを実行するとそこまでのViewが描画しない（＝関与しない）領域を持つ。

次の例では、fillではなくforegroundColorを使い、メソッドチェーンをシンプルなViewとして確認することにした。

```Swift
struct ContentView: View {
    var body: some View {
        VStack {
            HStack(spacing: 0) {
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.pink)
                    .background(.cyan)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.orange)
                    .background(.blue)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.yellow)
                    .background(.green)
            }
            HStack(spacing: 0) {
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.pink)
                    .background(.cyan)
                    .padding(20)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.orange)
                    .background(.blue)
                    .padding(20)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.yellow)
                    .background(.green)
                    .padding(20)
            }
            HStack(spacing: 0) {
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.pink)
                    .padding(20)
                    .background(.cyan)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.orange)
                    .overlay{Circle().fill(.pink)}
                    .padding(20)
                    .background(.blue)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.yellow)
                    .padding(20)
                    .background(.green)
                    .overlay{Circle().fill(.pink)}
            }
        }
        .padding(.vertical, 20)
    }
}
```

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3432313936342f66623638363761642d633531632d393734612d343135612d3463353263653463663761642e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421964/2972b7b8-f9d4-1247-0f23-32853eb1f2c0.png)

概ね想定通りのような気がする。

paddingを持つ子Viewがあると、親Viewはpaddingを除いた領域を各子Viewに提示をする。    
提示された子Viewは、paddingが実行されるまでpaddingによって狭められた領域内で最大の大きさでRoundedRectangleを描画しようとする。
この子Viewに対し、backgroundやforegroundが設定されていても、paddingが実行されるまではpaddingによって関与できない領域を作られた状態で描画をしようとする。
paddingが実行されるとpaddingの束縛からは解除されるが、親Viewに返した領域のサイズは変更されないため、親Viewに返した領域の最大の大きさでbackgroundやforegroundの描画をしようとする。


## frameによって子Viewの大きさを親Viewから指定する場合

frameを付けたらどうなるのかな、という疑問も同じように考えてみる。

### 仮説
frameは親Viewから提案される領域のサイズとし、frameが実行されるまでのViewに対する領域の提案とする。
paddingも同様であり、実行されるまでのViewに対して描画しない領域を与えるものとする。
ただし、どちらも親Viewには領域の結果を返してから描画するため、描画時の領域は変更されない。

frameのwidthを付けてしまったら自動的に領域の確保をしようとしなくなる（frameから与えられた領域内でしか動けないし、RoundedRectangleは与えられた領域のMaxを取ろうとするのでframeが与える領域より小さく確保しない）
前のプログラムと異なって、自動的に平等に領域を分配するのではなくframeが決めちゃうよ、という状態。

```Swift
struct ContentView: View {
    var body: some View {
        VStack {
            // 比較用
            HStack(spacing: 0) {
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.pink)
                    .background(.cyan)
                    .frame(width: 80)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.orange)
                    .background(.blue)
                    .frame(width: 80)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.yellow)
                    .background(.green)
                    .frame(width: 80)
            }
            // １
            HStack(spacing: 0) {
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.pink)
                    .background(.cyan)
                    .padding(20)
                    .frame(width: 80)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.orange)
                    .background(.blue)
                    .padding(20)
                    .frame(width: 80)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.yellow)
                    .background(.green)
                    .padding(20)
                    .frame(width: 80)
            }
            // 2
            HStack(spacing: 0) {
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.pink)
                    .background(.cyan)
                    .frame(width: 80)
                    .padding(20)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.orange)
                    .background(.blue)
                    .frame(width: 80)
                    .padding(20)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.yellow)
                    .background(.green)
                    .frame(width: 80)
                    .padding(20)
            }
        }
        .padding(.vertical, 20)
    }
}
```

![スクリーンショット 2023-05-02 11.46.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421964/b7f46123-39d8-1ac6-7c2a-8c04f31ebd06.png)

1. frameが登場するまでの子Viewに対し、frameのサイズを提案する。
その後にpaddingが来るのであれば、frameのが提案する領域内でpaddingの領域を確保し、その後paddingによって描画しない領域を決める。（HStackによって提案される領域ではなくframeによって提案される領域であるため、frameによって指定された値の中でpaddingが行われるということ）
場合によっては子Viewは消滅する。ScrollViewでも表示されない。  
上記の1のHstackの全てのRoundedRectangleに対しpaddingを40に設定すると、frameとしての領域は残るがpaddingによってRoundedRectangleは描画出来ない状態（消滅状態）となる。

2. paddingが登場するまでのViewに対し、描画しない領域を定める(HStackが親Viewであり、まだframeから新たに領域が提案されていない状態）。
frameはpaddingが描画しない領域を定めたViewに対し、frameが持つ領域を提案する。  
この時確保しようとするframe+paddingの値によって親Viewのサイズを超えたとしてもframe+paddingが確保できるまで無制限で実施しようとする。
こちらの場合、何がなんであろうとframeのサイズは確保しようとするためViewはpaddingによって押しつぶされない（画面外に追い出されはする）  
画面外とはいえ描画はされているので、ScrollView等を使えばスクロールで表示することができる。


![PNGイメージ-2A7EEC79CE13-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421964/fb283170-0899-45ac-0a2c-4ace74c40177.png)

![PNGイメージ-5C676A5CBA55-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421964/a49c4441-bc7e-c6ae-1877-3a005ed2f0bc.png)


paddingは確保しようとする力が強すぎて、指定する場所次第で自Viewすら抹消しようとするということがわかった。怖い。

## つまり？

paddingは親Viewから提案される前にpadding分を確保しようとする性質がある。
frameが先に来るならば、frameが確保した範囲内（frameが親View状態）から、子Viewにframeから提案される前の領域にpaddingを確保する。（frame(width:80）となっている場合、frameが確保している範囲内で子Viewに新しく領域を提案しようとしている状態なので、新しく領域を提案される際に子Viewのpaddingが確保される）
その後、提案された領域内で、確保したpadding分に「関与させない」処理を加える。

んじゃないかな…。正解がわからない。


## spacingはどうなのよ

Stack系のspacingはとどのつまりSpacer().frame(width: デフォルト値)もしくはSpacer().frame(height: デフォルト値)が間に入っている状態とすれば、Stack内でのspacingの強さはframeを持っているViewと同程度。frameを持っていないとspacingの値を親Viewから提示された領域内で確保しようと子Viewを押しつぶす。場合によってはspacingによって押しつぶされて子Viewは消滅する。  
frameを持っているならばframe+spacingの数だけ領域を確保しようとして、親Viewの領域を突き抜ける。
こちらも、ScrollView等を使えばスクロールで表示することができる。

```Swift
struct ContentView: View {
    var body: some View {
        VStack {
            HStack(spacing: 10) {
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.pink)
                    .background(.cyan)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.orange)
                    .background(.blue)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.yellow)
                    .background(.green)
            }
            HStack(spacing: 100) {
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.pink)
                    .background(.cyan)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.orange)
                    .background(.blue)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.yellow)
                    .background(.green)
            }
            HStack(spacing: 200) {
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.pink)
                    .background(.cyan)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.orange)
                    .background(.blue)
                RoundedRectangle(cornerRadius: 30)
                    .foregroundColor(.yellow)
                    .background(.green)
            }
        }
        .padding(.vertical, 20)
    }
}
```
![スクリーンショット 2023-05-02 12.44.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421964/2ba3e7ad-3247-589a-320a-51867aeb436e.png)


----

## まとめ

**padding**
子View 「親Viewさん！ 自分は自分の領域として絶対に使ってほしくない領域(padding)があって、その部分を確保してほしいんですけど！」
親View「OK。じゃあその部分を確保した状態でのこった領域で各子Viewに振り分けるよ。padding分は上乗せしておくよ」

**spacing**
親View「お前らの間隔は10な（こちらでSpacer().frame(width: 10)を入れるぞ)」
子Viewｓ「OK〜」

**Spacer()**
親View「Spacer()君も君たちViewとして平等に子Viewとして扱うからな」
子Viewｓ「OK〜。でも僕たちの領域を確保した状態でSpacer()君に割り振ってね！」
Spacer()「親Viewさん！ 僕のminLengthは絶対に確保してね」

```Swift
struct ContentView: View {
    var body: some View {
        
        GeometryReader { proxy in
            ScrollView {
                let width = proxy.size.width / 2.5
                VStack (spacing: 20){
                    Spacer()
                        .frame(height: 10)
                    HStack (spacing: 0) {
                        Text("▲")
                            .rotationEffect(.degrees(270))
                            .offset(x: 2)
                            .foregroundColor(.pink)
                        Text("あめんぼあかいなあいうえお。うきもにこえびもおよいでる")
                            .padding(20)
                            .background(.pink)
                        Spacer(minLength: width)
                    }
                    
                    HStack (spacing: 0) {
                        Text("▲")
                            .rotationEffect(.degrees(270))
                            .offset(x: 2)
                            .foregroundColor(.orange)
                        Text("かきくけこ")
                            .padding(20)
                            .background(.orange)
                        Spacer(minLength: width)
                    }
                    
                    HStack (spacing: 0) {
                        Text("▲")
                            .rotationEffect(.degrees(270))
                            .offset(x: 2)
                            .foregroundColor(.yellow)
                        Text("かきのきくりのきかきくけこ。きつつきこつこつかれけやき。\nささげにすをかけさしすせそ。そのうおあさせでさしました。")
                            .padding(20)
                            .background(.yellow)
                        Spacer(minLength: width)
                    }
                    
                    HStack (spacing: 0) {
                        Text("▲")
                            .rotationEffect(.degrees(270))
                            .offset(x: 2)
                            .foregroundColor(.red)
                        Text("とてとてたった")
                            .padding(20)
                            .background(.red)
                        Spacer(minLength: width)
                    }
                    Spacer()
                    
                    HStack (spacing: 0) {
                        Text("▲")
                            .rotationEffect(.degrees(270))
                            .offset(x: 2)
                            .foregroundColor(.cyan)
                        Text("")
                            .padding(20)
                            .background(.cyan)
                        Spacer(minLength: width)
                    }
                    Spacer()
                }
                
            }
        }
    }
}
```

![スクリーンショット 2023-05-02 13.19.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421964/24b7ce9f-7a5d-6d70-8099-51dbd0800ca2.png)


### 誰か教えて…
考えれば考えるほど沼にハマっている気がしている。paddingって何…。