[SwiftUI] AlignmentGuide でできること/できないこと

SwiftUI

alignmentGuide を使うことで、同一のStackに含まれないビューの配置を揃えることができます。

通常の Stack を使うと、その中でしか配置を揃えることができませんから、非常に有効な機能です。ただ、alignmentGuide で揃えることができるケースは限定されています。

できることとできないことを説明していきます。

alignmentGuide で揃えることができる例

複数のビューが異なる Stack 内に存在し、そのビューの配置を揃えたいとき

以下の例では、テキストを水平方向に2つ配置し、それを垂直方向に2つ重ねています。

# わかりやすくするために、各ビューにボーダーを設定して表示しています。

example for alignmentGuide step0

サンプルコード

struct ContentView: View {
    @State private var name = ""
    @State private var mail = ""
    var body: some View {
        VStack(alignment: .field) {
            HStack() {
                Text("Name")
                    .border(Color.blue)
                TextField("name", text: $name)
                    .frame(maxWidth: 300)
                    .alignmentGuide(.field) { (d) -> CGFloat in return d[.leading]}
                    .border(Color.green)
            }
            HStack() {
                Text("Mail")
                    .border(Color.blue)
                TextField("mail", text: $mail)
                    .frame(maxWidth: 300)
                    .alignmentGuide(.field) { (d) -> CGFloat in return d[.leading]}
                    .border(Color.green)
            }
        }
        .border(Color.red)
    }
}

このケースでは、Text と TextField は、(共通の親ビューである) HStack の alignment を指定して、設定することができます。

しかし、TextField 同士の配置を揃えることは、HStack, VStack いずれの alignment を使っても指定することができません。

そこで、登場するのが、alignmentGuide となるわけです。

例えば、以下のような 独自alignment を定義して使うことができます。

コード

extension HorizontalAlignment {
    struct Field: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            d[.leading]
        }
    }

    static let field = HorizontalAlignment(Field.self)
}

この定義は、水平方向の alignment として、新しく、"field" というものを定義しています。水平方向の alignment なので、VStack の alignment として指定することができます。

この例でいうと、外側の Stack に対して、新しい alignment を定義したことになります。

言い換えますと、共通の親ビューでの指定で、alignment を使って揃えることができます。

この場合で、2つある TextField を以下のように揃えようとすると、

TextFieldで揃え

以下のようなコードになります。

コード

struct ContentView: View {
    @State private var name = ""
    @State private var mail = ""
    var body: some View {
        VStack(alignment: .field) {
            HStack() {
                Text("Name")
                    .border(Color.blue)
                TextField("name", text: $name)
                    .frame(maxWidth: 300)
                    .alignmentGuide(.field) { (d) -> CGFloat in return d[.leading]}
                    .border(Color.green)
            }
            HStack() {
                Text("Mail")
                    .border(Color.blue)
                TextField("mail", text: $mail)
                    .frame(maxWidth: 300)
                    .alignmentGuide(.field) { (d) -> CGFloat in return d[.leading]}
                    .border(Color.green)
            }
        }
        .border(Color.red)
    }
}

新しく作成した alignmentGuide を使って、TextFieldの先頭位置で合わせています。

このように alignmentGuide を使うと、Stackされている複数要素の途中の位置でも合わせることができます。




alignmentGuide で揃えることができない例

では、alignmentGuide で揃えることができないのは、どういう時かというと、Stack されている複数要素に対して、複数の位置合わせを行うことができません。

先の例でいうと、TextField 同士の先頭をあわせるだけでなく、Text 同士の先頭位置も合わせようとして、別の alignmentGuide を作ってもうまく動作しません。

Text用のalignmentGuideを追加定義

extension HorizontalAlignment {
    struct Title: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            d[.leading]
        }
    }

    static let title = HorizontalAlignment(Title.self)

    struct Field: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            d[.leading]
        }
    }

    static let field = HorizontalAlignment(Field.self)
}

.field と .title の2つの alignment を使うためには、2つの VStack が必要になりますので、以下のようなコードになります。

コード

struct ContentView: View {
    @State private var name = ""
    @State private var mail = ""
    var body: some View {
        VStack(alignment: .title) {
            VStack(alignment: .field) {
                HStack() {
                    Text("Name")
                        .alignmentGuide(.title) { (d) -> CGFloat in d[.leading]}
                        .border(Color.blue)
                    TextField("name", text: $name)
                        .frame(maxWidth: 300)
                        .alignmentGuide(.field) { (d) -> CGFloat in return d[.leading]}
                        .border(Color.green)
                }
                HStack() {
                    Text("Mail")
                        .alignmentGuide(.title) { (d) -> CGFloat in d[.leading]}
                        .border(Color.blue)
                    TextField("mail", text: $mail)
                        .frame(maxWidth: 300)
                        .alignmentGuide(.field) { (d) -> CGFloat in return d[.leading]}
                        .border(Color.green)
                }
            }
        }
        .border(Color.red)
    }
}

上記のコードを実行すると、以下のようになります。

TextField のみ揃えられている

TextField しか揃えられていないのがわかるかと思います。

.title と .field の順番を入れ替えて(VStack の順番を入れ替えて)も以下のようになってしまいます。

Textのみ揃えられている

つまり、.alignmentGuide に一番近い、該当方向のStack からのみ alignmentGuide は、有効になるということです。

# そこで使われなかった alignmentGuide は、無視されるということです。

Storyboard を使っていたときは、このような制約は非常に簡単に設定できたことを考えると、レイアウトの柔軟性という観点では、SwiftUI は、まだ改善途中に思えます。




まとめ: alignmentGuide でのできること/できないこと

上記でみたように、alignmentGuide を使うことで、Stackに一要素に対して、位置調整を行うことができます。

しかし、Stackに含まれる複数の要素に、それぞれ個別の位置調整を行うことは、alignmentGuide ではできません。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です