Sponsor Link
環境&対象
- macOS Monterery beta 2
- Xcode 13 beta2
- iOS 15 beta
Picker
ユーザーに選択してもらうときに、Picker は、便利なコントロールです。
.pickerStyle(SegmentedPickerStyle())
上記の View Modifier を指定すると、以下ような表示に切り替わり、選択肢が全て見える状態で選択できるようになります。

複数行にわたる Picker の必要性
上記のスクリーンショットでも少し狭く感じますが、選択要素の表示幅が大きくなったり、選択要素数が増えると、SegmentedPickerStyle は、うまく表示できなくなります。
Wheel タイプでは発生しない問題ですが、すべての選択肢が見えるように SegmentedPickerStyle を設定した Picker を使いたい時に困ります。
オプションを確認してみましたが、Picker を複数行にするオプションは無いようです。
ということで、作ってみます。
使用するコントロール
Picker は使えない
Picker の引数を確認するとわかりますが、selection の引数は、optional ではありません。つまり常に何かが選択されていないといけません。
Picker を複数行にするということは、1行目で何かが選択されたときは、2行目の要素すべてが未選択になり、2行目で選択されているときは、1行目では未選択になる必要があります。つまり、Picker を並べて、複数行の Picker を作成することは出来そうもありません。
Picker 以外のコントロールを検討する
Value Selector として分類されている Toggle, Slider, Stepper, (Picker), DatePicker, ColorPicker は使えそうもありません。
List はどうか
選択することができるような他のコントロールということで List を使えないか考えました。selection はオプショナルですので、未選択の行が存在しても大丈夫です。
しかし List は、縦方向に要素がならび、行を選択するという固定の振る舞いをするコントロールでした。そのため、要素を横方向に並べて 個別要素を選択することは出来ないようです。
(List を横方向にする View Modifier があるかと思ったのですが、ありませんでした)

LazyHGrid と Button
ということで、たどり着いたのが、LazyVGrid と Button の組み合わせです。
LazyVGrid を使うことで、横方向に要素を展開することができます。必要に応じて、複数行に展開させることができます。
LazyVGrid には、selection がありませんので、表示要素を Button にして、その action を使って selection を管理することにしてみます。

以下のようなコードで実現しています。
struct MultiLinePicker: View {
@Binding var distance: Int
let primeNumbers = [3,5,7,11,13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
let columns = Array(repeating: GridItem(.fixed(80)), count: 8)
var body: some View {
LazyVGrid(columns: columns) {
ForEach(primeNumbers, id:\.self) { num in
Button(action: {
distance = num
}, label: {
Text("\(num)")
})
}
}
}
}
LazyVGrid + Button 実装での要改善点
以下が、要改善な点です。
- どの要素が選択されているかがわからないので、わかるようにしたい
- 選択要素数 や 要素の表示幅 が変わるケースに対応するには、LazyVGrid に指定する列数を一般化する必要がある
- Picker には、複数行にするオプションはない
- LazyVGrid + Button で実装する
- UI 細部を詰める必要がある
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Sponsor Link


