SwiftUI における Alignment とは

Alignment とは要するに「右揃え」「中央揃え」「左揃え」のようなテキストやオブジェクトなどのグループをどこを基準に揃えるかというパラメータのことである。

で、Alignment についてはSwiftUI の Alignment Guide まとめ (opens new window)の記事で@shiz 氏がまとめてくれていたりする。

ただ、それを読んだだけでは自分がしっかりと理解できなかったのでその解説となります。

コードから理解する

各コードと、その時どんなふうに描画されるかを確認して見ましょう。

// 基本コード
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
    }
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
1
2
3
4
5
6
7
8
9
10
11

コードについて

以後、これのbodyだけを変えたものを書いていきます。

Text を Stack 要素に入れる

以下の四つは全く同じ見た目になります。つまり、Stack 内に入れるデータが一つしかない場合は見た目の変化が起こりません。

ちなみに見た目としては画面中央揃えでテキストが表示されます。

var body: some View {
    Text("Hello, world!")
}
var body: some View {
    HStack {
        Text("Hello, world!")
    }
}
var body: some View {
    VStack {
        Text("Hello, world!")
    }
}
var body: some View {
    ZStack {
        Text("Hello, world!")
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

テキストが一つ - HStack VStack ZStack
全体の Alignment 中央揃え 中央揃え 中央揃え 中央揃え
文字の Alignment 中央揃え 中央揃え 中央揃え 中央揃え

Text 二つを Stack に入れる

テキストを二つ入れると、それぞれの Stack の特徴はでるようになりますが、やはり全体としては中央揃えになります。

なんでなんだろうなあと考えてみたのですが、HStack や VStack などはbodyに対して何の Alignment もかかっていないので変化しないのだと思います。

つまり、SwiftUI においては何もしなければ「中央揃え」が適用されるということになります。

テキストが二つ - HStack VStack ZStack
全体の Alignment 中央揃え 中央揃え 中央揃え 中央揃え
文字の Alignment 中央揃え 中央揃え 中央揃え 中央揃え

Stack に Alignment をつける

オプションで各 Stack にはContainer Alignmentをつけることができます。

ただし、HStack にはleadingはつけられませんし、VStack にtopを指定することはできません。これってちょっと不便な気がするのですが、どうなんでしょう?

Container Alignment HStack VStack ZStack
leading -
center -
trailing -
top -
center -
bottom -

VStack のイメージはこんな感じで、何もしなければコンテナ自体の大きさは可変で中に入れる要素ピッタリの大きさまで広がります。

コンテナ自体の大きさが可変であるため「コンテナ内の上から順番に積み上げていく」という意味であるtopを指定することができないというわけです。

しかし、SwiftUI にはFrame Alignmentというものがあるので、例えばframe(height: 200)などを指定してコンテナのサイズを固定させて上から積み上げていくというのは需要がありそうな気もします。

middle じゃないじゃん

何も考えずに垂直方向の中央揃えを middle って書いてましたが、実際には center になります。

Stack に Frame Alignment をつける

というわけで VStack に対してFrame Alignmentを適用してみます。

var body: some View {
    VStack(alignment: .center) {
        Text("Hello, world!")
        Text("by tkgstrator.work")
    }
    .frame(alignment: .leading)
}
1
2
3
4
5
6
7

ただし、このコードだと.frame(alignment: .leading)は何の効果も持たず、centerを指定した場合と全く同じ見た目になります。

ただし、以下のようにFrame Alignmentに対してwidth要素を付けると見た目が変わります。

var body: some View {
    VStack(alignment: .center) {
        Text("Hello, world!")
        Text("by tkgstrator.work")
    }
    .frame(width: 300, alignment: .leading)
}
1
2
3
4
5
6
7

何故なら VStack の幅は可変で常に内部のオブジェクトぴったりになるように大きさが変わっているからです。つまり、VStack(または HStack に対して)Frame Alignmentだけを指定するのは全く意味がありません。ダグドリオに十万ボルトするくらい意味がないです。

Frame Alignment について

幅を指定しないときは VStack と Frame の幅は完全に一致しています。

なので、Frame 内で VStack を左寄せすることはできません。Frame 要素の幅を指定することで VStack を Frame 内で幅寄せすることができるようになるわけです。

点線の部分は実際には見えないので、オブジェクトだけが左に寄ったように見えるわけです。

// 確認用コード
var body: some View {
    VStack(alignment: .center) {
        Text("Hello, world!")
        Text("by tkgstrator.work")
    }
    .background(Color.gray.opacity(0.3))
    .frame(width: 200, alignment: .leading)
    .background(Color.red.opacity(0.3))
}
1
2
3
4
5
6
7
8
9
10

実際に表示してみるとこのようになります。赤い背景が Frame でが設定された上で全体の中央になっており(デフォルト)、灰色の背景の VStack が Frame 内で左寄せになり、その中のテキストが中央揃えになっていることがわかります。

テキストで空文字を表示する

VStack はサイズを自動的に変えると説明したが、これが逆にめんどくさい仕様になる場合もある。

例えば、何かのフラグが有効のときだけ「有効」と表示し、そうでないときは何も表示しないようなコードを考える。

struct ContentView: View {
    @State private var isPresented: Bool = false
    var body: some View {
        VStack {
            VStack(alignment: .center) {
                Text("Hello, world!")
                Text("by tkgstrator.work")
                Text(isPresented ? "ENABLED" : "")
            }
            .background(Color.gray.opacity(0.3))
            .frame(height: 200, alignment: .top)
            .background(Color.red.opacity(0.3))
            Button(action: { isPresented.toggle() }, label: { Text("SWITCH") })
        }
        .background(Color.blue.opacity(0.3))
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

これはボタンを押せばisPresentedの値が切り替わり、切り替わる度にテキストが空文字から"ENABLED"の表示を交互に切り替えるプログラムである。