SwiftUI: Editable TextFields in Lists

535 Views Asked by At

*** NOTE: This question concerns macOS, not iOS ***

Context:

I have a list of items that serves as a "Master" view in a standard Master-Detail arrangement. You select an item from the list, and the detail view updates:

enter image description here

In SwiftUI, this is powered by a List like this:

struct RuleListView: View
{
    @State var rules: [Rule]
    @State var selectedRuleUUID: UUID? 
    
    var body: some View
    {
        List(rules, id: \.uuid, selection: $selectedRuleUUID) { rule in 
            RuleListRow(rule: rule, isSelected: (selectedRuleUUID == rule.uuid))
        }
    }
}

The Problem:

The name of each item in the list is user-editable. In AppKit, when using NSTableView or NSOutlineView for the list, the first click on a row selects that row and the second click would then begin editing the NSTextField that contains the name.

In SwiftUI, things have apparently gotten dumber. Clicking on ANY text in the list immediately begins editing that text, which makes selecting a row VERY difficult—you have to click on the 2 pixels above or below the TextField in each row.

To combat this, I've stashed a clear Button() on top of every row that's not selected. This button intercepts the click before it reaches the TextField() and selects the row. When the row is selected, the Button() is no longer included and subsequent clicks then select the TextField():

struct RuleListRow: View
{
   var rule: Rule
   var isSelected: Bool

   var body: some View
   {
       ZStack
       {
            Label {
                TextField("", text: $rule.name)
                    .labelsHidden()
            } icon: {
                Image(systemName: "lasso.sparkles")
            }
            
            if !isSelected
            {
                Button {
                    // No-op
                } label: {
                    EmptyView()
                }
                .buttonStyle(.plain)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.clear)
                .contentShape(Rectangle())
            }
        }
   }
}

Question:

The above works, but...this can't be correct, right? I've missed something basic that makes List behave properly when it contains TextField rows, right? There's some magic viewModifier I'm supposed to stick somewhere, I'm sure.

What is the correct, canonical way to solve this issue using Swift 5.5 and targeting macOS 11.0+?


Note:

My first approach was to simply disable the TextField when the row isn't selected, but that caused the text to appear "dimmed" and I couldn't find a way to override the text color when the TextField is disabled.

0

There are 0 best solutions below