2

*** 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.

Bryan
  • 4,628
  • 3
  • 36
  • 62
  • 3
    Not a direct answer to your question, but in my experience, although much of SwiftUI absolutely is production-ready, `List` on macOS is an uphill battle and you may be better off wrapping `NSTableView` than fighting `List` (this likely won't be the only issue you encounter with it). – jnpdx May 16 '22 at 05:04
  • Works as expected (at least at my replication) - first click selects row, second one activates editing. Xcode 13.3 / macOS 12.3.1 – Asperi May 16 '22 at 05:31
  • @Asperi - If I click anywhere in the row that is NOT text, the first click selects the row. But clicking on the TextField itself in a non-selected row edits the field without selecting the row. – Bryan May 16 '22 at 05:48
  • Is there a reason you are using a `List` instead of a `LavyVStack` ? – yawnobleix May 16 '22 at 05:51
  • @yawnobleix - Yes. `List` supports selection. `LazyVStack` does not (without a bunch of manual work--precisely the thing SwiftUI is supposed to eliminate). – Bryan May 16 '22 at 05:58
  • Then needed minimal complete reproducible example, probably some other your code introduces conflict or something. – Asperi May 16 '22 at 06:04

0 Answers0