Zenn
😬

[SwiftUI] Spacerはなぜいい感じに伸びてくれるのか

2025/01/29に公開
2

要約

SwiftUIでSpacerを使うと、ViewとViewの間のスペースを自身が伸びたり縮んだりして調整してくれる。
その挙動を、layoutPriorityの点から解説する。

Spacerとは

Spacerは、SwiftUIのViewのひとつで、HStackVStackの中で使われることが多い。
たとえば、要素を均等な間隔で配置したり、中央に配置する場合に使用する。

TextSpacerからなるHStack`

たとえば、2つのTextHStackで横に並べる場合、

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)

ColorSpacerからなるHStack

HStack {
    Text("Hello")
        .border(.red)
    Spacer()
    Color.blue
}
.frame(width: 300, height: 100)
.border(.green)

ColorTextの間にSpacerを挟むと、Spacerが縮み、Colorが最大まで大きくなる。

Text + Spacer + Textの場合は、Spacerが良い感じに伸びていて、
Text + Spacer + Colorの場合は、Spacer良い感じに縮んでいるように感じる。

これらの挙動は、すべてSpacerlayoutpriorityの値によるものである。

LayoutPriorityとは

SpacerのサイズとLayoutPriority

Spacerには、以下のサイズとlayoutpriorityが設定されている。

  • minWidth: 8, minHeight: 8 ← どんなに小さくても、このサイズになる。
  • idealWidth: 8, idealHeight: 8 ← 特に制約がなければ、このサイズになろうとする。
  • maxWidth: inf, maxHeight: inf ← 広がれるなら、どこまでも広がる。
  • layoutPriority: -inf ← ほかのどんなViewよりも、余ったスペースに広がる優先度が低い。

挙動の説明

これを踏まえると、上記の挙動が理解できる。
ポイントとして、

Text + Spacer + Textの場合

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

Color + Spacer + Textの場合


Textは、maxWidthが指定されていて、Hello39程度である。
Colorには、maxWidthinfで指定されているため、どこまでも広がることができる。
余ったスペースを埋めることができるViewのうち(Color, Spacer)、一番layoutPriorityが高いのはColorである。
Text39SpacerminWidthである8を除いた部分が、Colorに割り当てられる。
そのため、Colorが最大まで広がり、Spacerが縮む。

まとめ

Spacerは、layoutPriority = -infminWidth = 8minHeight = 8をもつViewである。
HStackの中のViewは、layoutPriorityが高い順に余ったスペースが与えられる。

これらより、Spacerの挙動としては、

  • maxWidthが固定のViewなどと一緒にHStackに入れたときは、余ったスペースを埋めるように広がる。(e.g. icon, Text)
  • maxWidthinfの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")
    }
}

Ref

2

Discussion

ログインするとコメントできます