子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)
}
}
}
-
Rectangle系は親Viewから与えられた領域一杯を使って描画しようとする性質がある。
つまり、同じlayoutPriorityであれば領域は等しく与えられ、大きさは同一になる -
HStack内に与えられた子Viewのうち、1つだけlayoutPriorityが高い状態になるならば(優先されるならば)
spacingの領域を除いて他のHStackの領域は消失する
ここまでは想定通り。 -
だが、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)
}
}
概ね想定通りのような気がする。
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)
}
}
-
frameが登場するまでの子Viewに対し、frameのサイズを提案する。
その後にpaddingが来るのであれば、frameのが提案する領域内でpaddingの領域を確保し、その後paddingによって描画しない領域を決める。(HStackによって提案される領域ではなくframeによって提案される領域であるため、frameによって指定された値の中でpaddingが行われるということ)
場合によっては子Viewは消滅する。ScrollViewでも表示されない。
上記の1のHstackの全てのRoundedRectangleに対しpaddingを40に設定すると、frameとしての領域は残るがpaddingによってRoundedRectangleは描画出来ない状態(消滅状態)となる。 -
paddingが登場するまでのViewに対し、描画しない領域を定める(HStackが親Viewであり、まだframeから新たに領域が提案されていない状態)。
frameはpaddingが描画しない領域を定めたViewに対し、frameが持つ領域を提案する。
この時確保しようとするframe+paddingの値によって親Viewのサイズを超えたとしてもframe+paddingが確保できるまで無制限で実施しようとする。
こちらの場合、何がなんであろうとframeのサイズは確保しようとするためViewはpaddingによって押しつぶされない(画面外に追い出されはする)
画面外とはいえ描画はされているので、ScrollView等を使えばスクロールで表示することができる。
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)
}
}
まとめ
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()
}
}
}
}
}
誰か教えて…
考えれば考えるほど沼にハマっている気がしている。paddingって何…。






