🦋

SwiftUI: LabeledContentの不便な挙動を制御する

2024/02/21に公開

macOSでLabeledContentを使うとViewの左側にラベルをつけることができます。

シンプルな例
struct ContentView: View {
    @State var isOn: Bool = false

    var body: some View {
        LabeledContent {
            Toggle(isOn: $isOn) {
                Text("Detail")
            }
        } label: {
            Text("Item:")
        }
    }
}

さらに、Formで囲めば往年のmacOSアプリの設定画面などで見られるラベルの位置が揃ったUIを実装できます。

Formで囲まない場合
var body: some View {
    VStack {
        LabeledContent {
            Toggle(isOn: $isOn) {
                Text("Detail")
            }
        } label: {
            Text("Apple:")
        }

        LabeledContent {
            Toggle(isOn: $isOn) {
                Text("Detail")
            }
        } label: {
            Text("Banana:")
        }

        LabeledContent {
            Toggle(isOn: $isOn) {
                Text("Detail")
            }
        } label: {
            Text("Cherry:")
        }
    }
}


ラベルの位置がバラバラ

Formで囲む場合
var body: some View {
    Form {
        LabeledContent {
            Toggle(isOn: $isOn) {
                Text("Detail")
            }
        } label: {
            Text("Apple:")
        }

        LabeledContent {
            Toggle(isOn: $isOn) {
                Text("Detail")
            }
        } label: {
            Text("Banana:")
        }

        LabeledContent {
            Toggle(isOn: $isOn) {
                Text("Detail")
            }
        } label: {
            Text("Cherry:")
        }
    }
}


ラベルの位置が綺麗に揃っている


そんな便利なLabeledContentですが、このcontentの中に複雑なViewを入れた時の挙動制御にコツが要ります。

例えば、VStackで複数のViewを入れる時何も対策しないとラベルの位置が上下中央寄せや下寄せになってしまうことがあります。

上下中央寄せになるパターン
LabeledContent {
    VStack(alignment: .leading) {
        Rectangle()
            .frame(width: 100, height: 100)
        Toggle(isOn: $isOn) {
            Text("Detail")
        }

    }
} label: {
    Text("Item:")
}


ラベルが上下中央寄せ

下寄せになるパターン
Form {
    LabeledContent {
        VStack(alignment: .leading) {
            Rectangle()
                .frame(width: 100, height: 100)
            Toggle(isOn: $isOn) {
                Text("Detail")
            }

        }
    } label: {
        Text("Item:")
    }
}


ラベルが下寄せ

Formに包むかどうかで挙動が変わっていますね。

色々試したところ、どうやらラベルを上寄せにしたい場合は、必ずFormの中に入れてcontent内の一番上にText属性のViewがあれば良いようです。なので、一番上にText属性を置きたくない場合はZStackを使いつつダミーのText.hidden()でおけば良いです。

Form {
    LabeledContent {
        ZStack(alignment: .top) {
            Text("dummy").hidden()
            VStack(alignment: .leading) {
                Rectangle()
                    .frame(width: 100, height: 100)
                Toggle(isOn: $isOn) {
                    Text("Detail")
                }
            }
        }
    } label: {
        Text("Item:")
    }
}


ラベルが上寄せ

なにこれ。

Discussion