TextField in List loses focus when ForEach adds rows

124 views Asked by At

I've read many similar questions here about ForEach causing TextField to lose focus, but all of those hinge on the ForEach using a binding parameter. This one doesn't.

I have a List with the first row as a TextField, and then a ForEach that shows rows based on matching items with the text in the TextField. Here's the code:

struct ContentView: View {
    @State private var textValue = ""

    var body: some View {
        List {
            // Text Entry
            TextField("Person:", text: $textValue)

            // Suggestions
            ForEach(suggestions, id: \.id) { onePerson in
                Text(onePerson.id)
            }
        }
    }
    
    var suggestions: [PersonSuggestion] {
        guard !textValue.isEmpty else { return [] }
        
        let existingPeople = Person.allCases
            .filter { $0.rawValue.localizedCaseInsensitiveContains(textValue) }
            .sorted(using: KeyPathComparator(\.rawValue))
            .map { PersonSuggestion.existing(person: $0) }
        
        if textValue != existingPeople.first?.id {
            return [.new(name: textValue)] + existingPeople
        } else {
            return existingPeople
        }
    }
}

And the supporting types:

enum Person: String, Hashable, CaseIterable {
    case John
    case Paul
    case George
    case Ringo
}

enum PersonSuggestion: Hashable, Identifiable {
    case new(name: String)
    case existing(person: Person)
    
    var id: String {
        switch self {
        case .new(let name):
            "+ " + name
        case .existing(let person):
            person.rawValue
        }
    }
}

When I enter the first letter into the text field, the ForEach goes from zero rows to a non-zero number (which is expected), and the text field loses focus (not expected). When I re-focus and then type more, and the number of rows goes down, the focus is not lost any more. It only gets lost when the text goes from empty to non-empty.

If I put the TextField and the ForEach in separate Sections, the problem doesn't occur. But I need all the rows to be in one section, so that's no good.

As stated at the top, all the other questions like this I've found on StackOverflow are about ForEach's that use a binding parameter. This one doesn't, and none of the mentioned solutions have worked. What am I missing here? Can you help me find a better way to fix this? :-)

2

There are 2 answers

1
Sweeper On BEST ANSWER

I don't think this is intentional - might be a SwiftUI bug.

This seems to be caused by giving an empty collection to ForEach. If you just do:

if !suggestions.isEmpty {
    ForEach(suggestions) { onePerson in
        Text(onePerson.id)
    }
}

then there is no problem, despite the code looking really stupid :D

0
J i N On

As you mentioned this approach prevents the 'TextField' from losing focus as each 'Section' is treated as a separate entity within the 'List'.

try this

struct ContentView: View {
@State private var textValue = ""

var body: some View {
    List {
        Section(header: Text("Person:")) {
            TextField("Enter a person:", text: $textValue)
        }

        Section(header: Text("Suggestions")) {
            ForEach(suggestions, id: \.id) { onePerson in
                Text(onePerson.id)
            }
        }
     }
  }
}