Open1

SwiftUI:Listの単一選択にチェックボックスをつける

kabeyakabeya

SwiftUIでListを選択可にした際、selectionの引数に渡す変数の型で、単一選択なのか複数選択なのかが変わります。

単一選択の場合は以下のような感じになります。

struct ContentView1: View {
    @State private var selectedItem: Int? = nil
    let items = ["大谷", "中谷", "小谷"]
     
    var body: some View {
        VStack {
            Text("選択されている担当者: \(selectedItem == nil ? "なし" : items[selectedItem!])")
            
            List(selection: $selectedItem) {
                ForEach(items.indices, id: \.self) { index in
                    Text(items[index]).tag(index)
                }
            }
            .environment(\.editMode, .constant(.active))
        }
    }
}

表示は以下のようになりますね。

行を選択するとその行が反転するだけ、という感じです。

複数選択の場合の表示は以下のようになります。

行頭に円が追加表示されて、選択するとその行が反転した上で円にチェックマークが入ります。

で、要は単一選択でも複数選択のようにチェックマークを付けたいじゃないかという話です。

struct ContentView2: View {
    @State private var selectedItems: Set<Int> = []
    let items = ["大谷", "中谷", "小谷"]
     
    var body: some View {
        VStack {
            let selectedItem = selectedItems.first
            Text("選択されている担当者: \(selectedItem == nil ? "なし" : items[selectedItem!])")
            
            List(selection: $selectedItems) {
                ForEach(items.indices, id: \.self) { index in
                    Text(items[index]).tag(index)
                }
            }
            .environment(\.editMode, .constant(.active))
        }
        .onChange(of: selectedItems) { oldSelection, newSelection in
            guard let toBeUnchecked = oldSelection.first else {
                return
            }
            if newSelection.count <= 1 {
                return
            }
            selectedItems.remove(toBeUnchecked)
        }
    }
}

.onChange(of:peform:)を使って2つ目が選択されたら先に選択されていた分を選択解除し、常に選択アイテムが1つ以下になるようにします。

.onChange(of:perform:)のクロージャはiOS17で変わったのでiOS16以前の場合は .onChange(of: selectedItems) { oldSelection, newSelection inの部分を.onChange(of: selectedItems) { [oldSelection = self.selectedItems] newSelection inのように書き換えれば良いでしょう。