0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

0
Last updated at Posted at 2023-05-02

子View同士の間隔はspacingを使うべきという話を読んだ

なるほど。

つまり、「親Viewから提示される領域の中で親Viewが提示する範囲と子Viewとの距離を決める機能がpaddingで、子View同士の距離を決めるのは親Viewから与えられるspacingであるべき」、と理解した。

とすると、「paddingは子Viewが完全に制御していて、親Viewは領域確保(提示)に関与しないのか?」
という疑問に陥った。

paddingはいつ決まる?

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

frameは指定しないで試す。

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

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

  2. HStack内に与えられた子Viewのうち、1つだけ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として確認することにした。

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

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

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が決めちゃうよ、という状態。

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)
            }
            // 1
            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

  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

PNGイメージ-5C676A5CBA55-1.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等を使えばスクロールで表示することができる。

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


まとめ

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

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

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

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

誰か教えて…

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

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?