🦋
SwiftUI: 複数選択可能なチェックボックス
Picker
にはradioGroup
というスタイルがあり、それを使えば複数の選択肢の中から1つを選択させるUIを簡単に実装できます。しかし、複数選択させたいチェックボックスを実装しようとすると意外とベタ実装しないといけません。なので、便利なコンポーネントCheckboxList
を作ってみました。
CheckboxList
を使えば、要素の配列(data
)と選択状態(selection
)とチェックボックスのラベルにしたいViewのクロージャー(rowContent
)を渡すだけで簡単に複数選択可能なチェックボックスを実装できます。(axis
も指定すればアイテムを並べる方向を指定できます。)
struct ContentView: View {
@State var items = [Item]()
@State var selection = Set<Item>()
var body: some View {
VStack(spacing: 20) {
CheckboxList(items, selection: $selection) { item in
Text("N.\(item.number)")
}
CheckboxList(items, selection: $selection, axis: .horizontal) { item in
Text("N.\(item.number)")
}
}
.padding()
.onAppear {
items = (0 ..< 5).map { Item(number: $0) }
}
}
}
CheckboxListの実装
CheckboxList
@MainActor
struct CheckboxList<Data, RowLabel>: View where Data : RandomAccessCollection, Data.Element : Identifiable & Hashable, RowLabel : View {
@Binding private var selection: Set<Data.Element>
private let axis: Axis
private let data: Data
private let rowLabel: (Data.Element) -> RowLabel
init(
_ data: Data,
selection: Binding<Set<Data.Element>>,
axis: Axis = .vertical,
@ViewBuilder rowLabel: @escaping (Data.Element) -> RowLabel
) {
self.axis = axis
self.data = data
_selection = selection
self.rowLabel = rowLabel
}
var body: some View {
switch axis {
case .horizontal:
HStack {
iteration
}
case .vertical:
Form {
iteration
}
}
}
private var iteration: some View {
ForEach(data) { element in
Toggle(isOn: Binding<Bool>(
get: { selection.contains(element) },
set: {
if $0 {
selection.insert(element)
} else {
selection.remove(element)
}
}
)) {
rowLabel(element).tag(element)
}
.toggleStyle(.checkbox)
}
}
}
実装はList
の定義を参考にしました。ヘッダーしか見えないので具体的な実装部分は予想なのですが、Genericsを駆使することでそこそこ万能な実装にできていると思います。
Discussion