[SwiftUI] Spacerはなぜいい感じに伸びてくれるのか
要約
SwiftUIでSpacerを使うと、ViewとViewの間のスペースを自身が伸びたり縮んだりして調整してくれる。
その挙動を、layoutPriorityの点から解説する。
Spacerとは
Spacerは、SwiftUIのViewのひとつで、HStackやVStackの中で使われることが多い。
たとえば、要素を均等な間隔で配置したり、中央に配置する場合に使用する。
TextとSpacerからなるHStack`
たとえば、2つのTextをHStackで横に並べる場合、
HStack {
Text("Hello")
.border(.red)
Text("World")
.border(.blue)
}.padding()
.frame(width: 300)
.border(.green)

Spacerを挟むことで、
Textを両端に配置したり
HStack {
Text("Hello")
.border(.red)
Spacer()
Text("World")
.border(.blue)
}.padding()
.frame(width: 300)
.border(.green)

Textを均等に配置したりできる。
HStack {
Spacer()
Text("Hello")
.border(.red)
Spacer()
Text("World")
.border(.blue)
Spacer()
}.padding()
.frame(width: 300)
.border(.green)

ColorとSpacerからなるHStack
HStack {
Text("Hello")
.border(.red)
Spacer()
Color.blue
}
.frame(width: 300, height: 100)
.border(.green)
ColorとTextの間にSpacerを挟むと、Spacerが縮み、Colorが最大まで大きくなる。

Text + Spacer + Textの場合は、Spacerが良い感じに伸びていて、
Text + Spacer + Colorの場合は、Spacerが良い感じに縮んでいるように感じる。
これらの挙動は、すべてSpacerのlayoutpriorityの値によるものである。
LayoutPriorityとは
SpacerのサイズとLayoutPriority
Spacerには、以下のサイズとlayoutpriorityが設定されている。
- minWidth:
8, minHeight:8← どんなに小さくても、このサイズになる。 - idealWidth:
8, idealHeight:8← 特に制約がなければ、このサイズになろうとする。 - maxWidth:
inf, maxHeight:inf← 広がれるなら、どこまでも広がる。 - layoutPriority:
-inf← ほかのどんなViewよりも、余ったスペースに広がる優先度が低い。
挙動の説明
これを踏まえると、上記の挙動が理解できる。
ポイントとして、
-
HStackではlayoutPriorityが大きいViewから余ったスペースが与えられる。(ref: https://zenn.dev/ueeek/articles/20250125my_hstack) -
LayoutPriorityを指定していないViewは、layoutPriorityが0となる。
Text + Spacer + Textの場合

Textは、maxWidthが指定されていて、Helloは39、Worldは45程度である。
そのため、width=300のHStackのうち、39 + 45の84がTextに割り当てられ、216が余る。
余ったスペースは、ほかにpriorityが高いViewが存在しないため、Spacerに割り当てられる。
これにより、Spacerが余ったスペースに広がり、Textが両端に配置される。
Color + Spacer + Textの場合

Textは、maxWidthが指定されていて、Helloは39程度である。
Colorには、maxWidthがinfで指定されているため、どこまでも広がることができる。
余ったスペースを埋めることができるViewのうち(Color, Spacer)、一番layoutPriorityが高いのはColorである。
Textの39とSpacerのminWidthである8を除いた部分が、Colorに割り当てられる。
そのため、Colorが最大まで広がり、Spacerが縮む。
まとめ
Spacerは、layoutPriority = -inf、minWidth = 8、minHeight = 8をもつViewである。
HStackの中のViewは、layoutPriorityが高い順に余ったスペースが与えられる。
これらより、Spacerの挙動としては、
-
maxWidthが固定のViewなどと一緒にHStackに入れたときは、余ったスペースを埋めるように広がる。(e.g. icon, Text) -
maxWidthがinfのViewなどと一緒にHStackに入れたときは、layoutPriorityの低さから、広がることができずに縮む。(e.g.frame()を指定してない、ColorやRectangle)
余談: LayoutPriorityや、min/ideal/max Sizeを調べた方法
Layout Protocolで使用される、Subview.sizeThatFitsで、min/ideal/maxの値を確認できる。
struct TestLayout: Layout {
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
print("# minSize \(subviews[0].sizeThatFits(.zero))")
print("# idealSize \(subviews[0].sizeThatFits(.unspecified))")
print("# maxSize \(subviews[0].sizeThatFits(.infinity))")
print("# layoutPriority \(subviews[0].priority)")
return .zero
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
// Empty
}
}
#Preview {
TestLayout {
Text("Hello")
}
}
Discussion