Home > database >  SwiftUI List: Prevent Empty Selection
SwiftUI List: Prevent Empty Selection

Time:02-14

I have a SwiftUI list, defined in a typical fashion:

struct SettingsView: View
{
    @State private var selectedCategory: SettingsCategory? = .general

    List(SettingsCategory.allCases, id: \.self, selection: $selectedCategory) { category in
        [...]
    }
}

In this case, the List is a table of "categories" for a settings area in my UI. The SettingsCategory is an enum that defines these categories, and the UI ends up looking like this:

enter image description here

It is not appropriate for this list to have an empty selection; a category should always be selected. In AppKit, it was trivially easy to disable an empty selection on NSTableView. But in SwiftUI, I've been unable to find a way to disable it. Anytime I click in the empty area of the list, the selection is cleared. How can I stop that?

selectedCategory must be an Optional or the compiler vomits all over itself.

I can't use willSet/didSet on selectedCategory because of the @State property wrapper. And I can't use a computed property that never returns nil because the List's selection has to be bound.

I also tried this approach: SwiftUI DatePicker Binding optional Date, valid nil

So, what magical incantation is required to disable empty selection in List?

CodePudding user response:

One solution would be to set the selection back to the original one if the selection becomes nil.

Code:

struct ContentView: View {
    @State private var selectedCategory: SettingsCategory = .general

    var body: some View {
        NavigationView {
            SettingsView(selectedCategory: $selectedCategory)

            Text("Category: \(selectedCategory.rawValue.capitalized)")
                .navigationTitle("App")
        }
    }
}
enum SettingsCategory: String, CaseIterable, Identifiable {
    case destination
    case general
    case speed
    case schedule
    case advanced
    case scripts

    var id: String { rawValue }
}
struct SettingsView: View {
    @Binding private var selectedCategory: SettingsCategory
    @State private var selection: SettingsCategory?

    init(selectedCategory: Binding<SettingsCategory>) {
        _selectedCategory = Binding<SettingsCategory>(
            get: { selectedCategory.wrappedValue },
            set: { newCategory in
                selectedCategory.wrappedValue = newCategory
            }
        )
    }

    var body: some View {
        List(SettingsCategory.allCases, selection: $selection) { category in
            Text(category.rawValue.capitalized)
                .tag(category)
        }
        .onChange(of: selection) { [oldCategory = selection] newCategory in
            if let newCategory = newCategory {
                selection = newCategory
                selectedCategory = newCategory
            } else {
                selection = oldCategory
            }
        }
    }
}

CodePudding user response:

you could try adding .onChange to the List, such as:

.onChange(of: selectedCategory) { val in
    if val == nil {
        selectedCategory = .general // <-- make sure never nil
    }
}
  • Related