feed

2022年10月31日, 編集履歴

SwiftUI macOSアプリの設定ウィンドウ項目をLabeledContentで整列させる

SwiftUI macOSアプリで、以下のような設定ウィンドウを作りたいとする。

ラベルは右揃えで、ポップアップメニューやテキストフィールド等の操作部品は左揃えにして整列させたい。VStackや単純なFormではきれいな整列ができない(……ことはないがややこしい黒魔術が必要となる)。よくあるパターンのレイアウトなのに変な感じだった。

そんな中、macOS 13.0から使える新しいLabeledContentFormと組み合わせることで、かんたんにきれいな整列が実現できるようになった。

環境は以下の通り。

VStackや単純なFormの場合

まずはこれまでの状態を確認してみる。UIを縦に並べていく場合、VStackFormを使う。VStackでは単純な左揃えや中央揃え等になってしまうので却下。こういった設定項目を縦に並べる場合はFormを使う。たとえば以下のような感じ。

Form {
  Picker("Pref 1:", selection: .constant(1)) {
    Text("Option 1").tag(1)
    Text("Option 2").tag(2)
  }
  .fixedSize()

  TextField("Preference 2:", text: .constant(""))
    .frame(width: 200)

  HStack {
    Text("Long Preference 3:")
    Toggle("Enabled Hoge", isOn: .constant(true))
  }

  HStack {
    Text("Pref 4:")
    Button("Button") { }
  }
}
.padding()

このとき、PickerTextFieldなど、標準で左側にラベル的要素を持つUIは意図通りきれいに整列される。しかし、左側にラベル的要素を持たないToggleButtonなどは、ラベル的要素を表現するためにHStackと組み合わせる必要があり、その場合、意図通りには整列されない。

macOS 12までは、Example of aligning labels in SwiftUI.Form on macOSにあるような.alignmentGuideを使うややこしい黒魔術的な手法で整列させなければならなかった。

LabeledContentの場合

macOS 13.0からLabeledContentが使えるようになった。これは前述のToggleButton等の、標準では左側にラベル的要素を持たないUI部品にラベルを付与できるようになる。そして、それをFormと組み合わせることできれいな整列をかんたんに実現できる。こんな感じ。

Form {
  Picker("Pref 1:", selection: .constant(1)) {
    Text("Option 1").tag(1)
    Text("Option 2").tag(2)
  }
  .fixedSize()

  TextField("Preference 2:", text: .constant(""))
    .frame(width: 300)

  LabeledContent("Long Preference 3:") {
    Toggle("Enabled Hoge", isOn: .constant(true))
  }

  LabeledContent("Pref 4:") {
    Button("Button") {}
  }
}
.padding()

前項のコード例と違うのは、ToggleButtonの部分でHStackの代わりにLabeledContentを使ってラベルを持たせている点。

特に黒魔術は必要なく、素直に意図通りのレイアウトが実現できる。SwiftUIもだんだん良くなってきた!

追記 .formStyle(.grouped)

macOS 13.0 VenturaからはFormに対して.formStyle()で表示形式を変更できるようになった。

Form {
  Picker("Pref 1", selection: .constant(1)) {
    Text("Option 1").tag(1)
    Text("Option 2").tag(2)
  }

  TextField("Preference 2", text: .constant(""))

  Toggle("Long Long Preference 3", isOn: .constant(true))

  HStack {
    Text("Pref 4")
    Spacer()
    Button("Button") { }
  }

  LabeledContent("Pref 5") {
    Button("Button") { }
  }
}
.formStyle(.grouped)

このように.formStyle(.grouped)を指定することで、Venturaの新しいシステム設定のような表示形式にできる。

この場合、Toggleは前述のLabeledContentを使わずとも自然に記述できる。Buttonに関してはHStackでラベル的要素を付けて整列できるが、LabeledContentを使った方が自然な記述ができる。