🦋

SwiftUI: 複数選択可能なチェックボックス

2024/10/21に公開

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