Sponsor Link
環境&対象
- macOS Big Sur 11.3
- Xcode 12.5
# iOS での使用と似ているとおもいますが、iOS では確認していません。
Picker
SwiftUI には複数から選択するための UI として Picker が用意されています。
Picker 使用例
以下が、Picker を使った簡単な例です。選択肢 0,1,2,3,4 から選択された情報が、selection0 に入ります。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2021/05/17
// © 2021 SmallDeskSoftware
//
import SwiftUI
struct ContentView: View {
let data = [0,1,2,3,4]
@State var selection0:Int = 0
var body: some View {
VStack {
HStack {
Picker(selection: $selection0, label: Text("Picker"), content: {
ForEach(data, id:\.self) { value in
Text("\(value)")
.tag(value)
}
})
}
}
.padding(50)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
しかし、Picker では、階層的な選択を実現することができません。
階層的な選択
選択肢によっては以下のような 階層的に選択できる UI が必要となります。
このような選択を Picker で行う場合には、以下のようになってしまいます。
動的に増える Picker による選択
動的に、Picker を増やすことで 階層的に選択できるようにしてみました。
コードは以下のようなコードになります。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2021/05/17
// © 2021 SmallDeskSoftware
//
import SwiftUI
struct ContentView: View {
let data = [0,1,2,3,4]
let option = [[0,1],[2,3,4],[5,6],[7,8,9], [10,11,12,13,14,15]]
@State var selection0:Int = -1
@State var selection1:Int = 0
@State var optionVisible = false
var body: some View {
VStack {
HStack {
Picker(selection: $selection0, label: Text("Picker"), content: {
ForEach(data, id:\.self) { value in
Text("\(value)")
.tag(value)
}
})
if optionVisible {
// (1)
Picker(selection: $selection1, label: Text("Option"), content: {
ForEach(option[selection0], id:\.self) { value in
Text("\(value)")
.tag(value)
}
})
}
}
.onChange(of: selection0, perform: { value in
// (2)
if selection0 != 3 {
optionVisible = true
selection1 = option[selection0][0]
} else {
optionVisible = false
}
})
}
.onAppear {
selection0 = 0
}
.padding(49)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
- 2つめの Picker を用意し、optionVisible 変数で表示を制御します
- onChange で選択肢の変更をチェックし、optionVisible を適切な値に変更します
ケースによるんでしょうけど、階層的メニューを期待しているとすこし残念な UI な気がします。
Menu
このようなケースで、Menu を使用することでも 階層的な選択肢を表示することが可能となります。
シンプルな Menu
まずは、シンプルに使用したメニューです。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2021/05/17
// © 2021 SmallDeskSoftware
//
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Menu {
Button(action: {
// do something
}, label: {
Text("Item1")
})
Button(action: {
// do something
}, label: {
Text("Item2")
})
} label: {
Text("Menu")
}
.padding(49)
}
}
Menu では、選択肢をボタンとして追加することで各メニュー選択時の動作を記述することができます。
Picker と Menu の相違点
Picker と Menu の類似点・相違点を挙げてみました。
- (類似点)Picker / Menu ともに、選択肢から選択させるための UI
- (類似点)Picker / Menu ともに、プルダウンメニューを作ることができる
- (相違点)Picker で要素した選択は、タイトルとして表示されるが、Menu では選択された Button の action が実行されるのみ
- (相違点)Picker での要素選択は、選択要素の持つ .tag 値が selection に設定される。Menu では選択された Button の action が実行されるのみ
- (相違点)Picker は、配下に Picker を持つことはできない。 Menu は、配下に階層的に Menu を持つことができる。
どちらも プルダウンメニューを作ることができるのですが、上記の相違点に気をつけて 適材適所で使う必要があります。
例えば、階層的プルダウンは、Picker では実現不可なので、 Menu を使用します。
Menu を使う時に注意すべき点
気をつけるべき点として、「Menu は 親 View との依存関係が 他の View とすこし異なる」があります。
うまくいかない例
例えば、Picker の代わりとしての使用を考えると、メニュータイトルを動的に変更することを考えると思います。
このメニュータイトルを @State 変数や @StateObject 変数を使って設定しても、期待通りに更新されません。
具体的には、選択された値が グレイアウトされて表示されてしまいます。
なぜか、再度プルダウンを行うと表示が正しくなります。
この振る舞いは、SwiftUI の実装から来ているようです。解消されるかは不明です。
解決策
Menu そのものを切り替えるようにして、強制的に 再構築させるようにします。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2021/05/17
// © 2021 SmallDeskSoftware
//
import SwiftUI
struct ContentView: View {
@State var menuTitle:String = "Menu"
@State var menuToggle = false
var body: some View {
VStack {
if menuToggle {
menu()
} else {
menu()
}
}
.padding(49)
}
func menu() -> some View {
Menu(menuTitle) {
Button(action: {
menuTitle = "Item1"
menuToggle.toggle()
}, label: {
Text("Item1")
})
Menu("Optional"){
Button(action: {
menuTitle = "Opt1"
menuToggle.toggle()
}, label: {
Text("Opt1")
})
Button(action: {
menuTitle = "Opt2"
menuToggle.toggle()
}, label: {
Text("Opt2")
})
}
Button(action: {
menuTitle = "Item2"
menuToggle.toggle()
}, label: {
Text("Item2")
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
まとめ:Picker と Menu の要点
- Picker は 選択のための専用 UI
- Menu は 選択から action を実行する UI
- action で値を設定することで、Menu は Picker 的に使用することができる
- Menu は、階層的に構築できる
- Menu の更新タイミングは、癖があるので、気をつける
SwiftUI 学習におすすめの本
SwiftUI 徹底入門
SwiftUI は、グラフィカルなライブラリということもあり、文字だけのテキストよりは、画像が多く入れられた書籍を読むと理解が進みやすいです。
自分で購入した中でおすすめできるものとしては、以下のものです。
2019 年発表の SwiftUI 1.0 相当を対象にしているので、2020/2021 に追加された一部の機能は、説明されていません。
ですが、SwiftUI 入門書としては、非常によくできていますし、わかりやすいです。 この本で学習した後に、追加分を学習しても良いと思います。
SwiftUIViewsMastery
英語での説明になってしまいますが、以下の本もおすすめです。
1ページに、コードと画面が並んでいるので、非常にわかりやすいです。
View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。
超便利です
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Sponsor Link