SwiftUI の alignment と alignmentGuide でカスタム配置する

SwiftUI のスタックは、子ビューを特定のルールに従って揃えます。デフォルトの中央揃えで事足りることも多いですが、複雑な UI では alignment の細かい制御が必要になります。さらに alignmentGuide を使えば、標準の揃え位置を独自にカスタマイズできます。

スタックの alignment 基本

VStack の alignment は水平方向(leading、center、trailing)、HStack の alignment は垂直方向(top、center、bottom、firstTextBaseline、lastTextBaseline)を指定します。

VStack(alignment: .leading, spacing: 8) {
    Text("タイトル")
        .font(.title)
    Text("少し短い")
        .font(.body)
    Text("もっと長いサブテキストがここに入ります")
        .font(.caption)
}

.leading を指定すると、幅の異なる 3 つの Text がすべて左端に揃います。フォームやカードのテキスト表示で最も使われるパターンです。

firstTextBaseline と lastTextBaseline

HStack で異なるサイズのテキストを並べるとき、.center だと見た目のバランスが崩れることがあります。そんなときは .firstTextBaseline が効果的です。

HStack(alignment: .firstTextBaseline) {
    Text("¥")
        .font(.body)
    Text("1,200")
        .font(.system(size: 48, weight: .bold))
    Text("税込")
        .font(.caption)
}

3 つのテキストがベースライン(文字の底辺)で揃うため、価格表示のように異なるフォントサイズが混在する場面でも自然な見た目になります。

center alignment

各ビューの中心を垂直方向で揃える。テキスト以外のビュー(画像やボタンなど)が混在する場合に適している

firstTextBaseline

最初の行のテキストベースラインで揃える。フォントサイズが異なるテキスト同士を揃えるときに自然な見た目になる

ZStack の alignment

ZStack の alignment は、重なったビューをどの方向に揃えるかを指定します。デフォルトは .center ですが、バッジやラベルを角に寄せたいケースでは別の値が必要です。

ZStack(alignment: .topTrailing) {
    RoundedRectangle(cornerRadius: 12)
        .fill(Color.gray.opacity(0.2))
        .frame(width: 120, height: 120)
    
    Text("3")
        .font(.caption2)
        .padding(6)
        .background(Color.red)
        .foregroundColor(.white)
        .clipShape(Circle())
}

通知バッジを右上に配置する典型的なパターンです。ZStack の alignment を変えるだけで、追加の offset なしにバッジが角に寄ります。

alignmentGuide で揃え位置をカスタマイズする

標準の alignment ではカバーしきれないケースでは、alignmentGuide モディファイアを使って揃え位置を手動で指定できます。

VStack(alignment: .leading) {
    Text("名前")
        .font(.caption)
        .foregroundColor(.secondary)
    
    Text("田中太郎")
        .font(.title2)
        .alignmentGuide(.leading) { d in
            d[.leading] - 4
        }
}

alignmentGuide のクロージャは ViewDimensions を受け取り、CGFloat を返します。この返り値が「そのビューの alignment 基準位置」になります。上の例では .leading の位置を 4pt 左にずらしており、微妙なインデント調整に使えます。

カスタム Alignment の定義

プロジェクト独自の揃え基準を作ることも可能です。たとえば、ラベルと値を特定の位置で揃えるカスタム alignment を定義してみましょう。

extension HorizontalAlignment {
    private enum ValueAlignment: AlignmentID {
        static func defaultValue(in context: ViewDimensions) -> CGFloat {
            context[.trailing]
        }
    }
    static let valueAlignment = HorizontalAlignment(ValueAlignment.self)
}

VStack(alignment: .valueAlignment, spacing: 8) {
    HStack {
        Text("身長")
        Text("170cm")
            .alignmentGuide(.valueAlignment) { d in d[.leading] }
    }
    HStack {
        Text("体重")
        Text("65kg")
            .alignmentGuide(.valueAlignment) { d in d[.leading] }
    }
}

カスタム alignment を定義すると、HStack を跨いで「値」の部分だけを縦に揃えることができます。設定画面やプロフィール表示のように、ラベルと値をきれいに整列させたい場面で威力を発揮するテクニックです。