I have a List
that gets data from my people
array and displays their names.
I'm also filtering the list so that it only shows the names that contain the text field's text, which is searchText
. Here's my code:
struct Person: Identifiable {
let id = UUID() /// required for the List
var name = ""
}
struct ContentView: View {
@State var searchText = ""
var people = [ /// the data source
Person(name: "Alex"),
Person(name: "Ally"),
Person(name: "Allie"),
Person(name: "Bob"),
Person(name: "Tim"),
Person(name: "Timothy")
]
var body: some View {
VStack {
TextField("Search here", text: $searchText) /// text field
.padding()
List {
ForEach(
people.filter { person in /// filter the people
searchText.isEmpty || person.name.localizedStandardContains(searchText)
}
) { person in
Text(person.name)
}
}
.animation(.default) /// add the animation
}
}
}
Without .animation(.default)
, it doesn't animate the changes (as expected).
With .animation(.default)
, it animates!
However, the problem happens when none of the people's names contain searchText
. When this happens, the people.filter
returns an empty array, and the List
freaks out. For example, when I type "q", this happens:
The entire list flies to the right, and zooms back in from the left when I delete "q". How can I prevent this from happening? I'm looking for a similar animation to the normal filtering animation (sliding up and disappearing, like in the second gif), or just fading it out.
I just tested on iOS 13 and if I remove .animation(.default)
, it works perfectly!
However, if I add .animation(.default)
again, I get the same result as in iOS 14.
My actual code groups the people, so I already use sections
in my List
.
struct Group: Identifiable {
let id = UUID() /// required for the List
var groupName = ""
var people = [Person]()
}
struct Person: Identifiable {
let id = UUID() /// required for the List
var name = ""
}
struct ContentView: View {
@State var searchText = ""
/// groups of people
var groups = [
Group(groupName: "A People", people: [
Person(name: "Alex"),
Person(name: "Ally"),
Person(name: "Allie")
]),
Group(groupName: "B People", people: [
Person(name: "Bob")
]),
Group(groupName: "T People", people: [
Person(name: "Tim"),
Person(name: "Timothy")
])
]
var body: some View {
VStack {
TextField("Search here", text: $searchText) /// text field
.padding()
List {
ForEach(
/// Filter the groups for people that match searchText
groups.filter { group in
searchText.isEmpty || group.people.contains(where: { person in
person.name.localizedStandardContains(searchText)
})
}
) { group in
Section(header: Text(group.groupName)) {
ForEach(
/// filter the people in each group
group.people.filter { person in
searchText.isEmpty || person.name.localizedStandardContains(searchText)
}
) { person in
Text(person.name)
}
}
}
}
.animation(.default) /// add the animation
}
}
}
If I wrap the ForEach
inside the List
as @mahan suggested, this happens:
The List
animates perfectly with no weird zoom animation, but the section headers lose their styles and look like normal rows. But I think we're getting close!
Wrap ForEach in a Section, and the issue will be fixed.
List {
Section {
ForEach(
people.filter { person in /// filter the people
searchText.isEmpty || person.name.localizedStandardContains(searchText)
}
) { person in
Text(person.name)
}
}
}
.animation(.default) /// add the animation
You can add as many sections as you wish so.
struct Person: Identifiable {
let id = UUID() /// required for the List
var name = ""
}
struct ContentView: View {
@State var searchText = ""
var people = [ /// the data source
Person(name: "Alex"),
Person(name: "Ally"),
Person(name: "Allie"),
Person(name: "Bob"),
Person(name: "Tim"),
Person(name: "Timothy")
]
var people2 = [ /// the data source
Person(name: "John"),
Person(name: "George"),
Person(name: "Jack"),
Person(name: "Mike"),
Person(name: "Barak"),
Person(name: "Steve")
]
var body: some View {
VStack {
TextField("Search here", text: $searchText) /// text field
.padding()
List {
Section {
ForEach(
people.filter { person in /// filter the people
searchText.isEmpty || person.name.localizedStandardContains(searchText)
}
) { person in
Text(person.name)
}
}
Section {
ForEach(people2.filter { person in /// filter the people
searchText.isEmpty || person.name.localizedStandardContains(searchText)
}) { person in
Text(person.name)
}
}
}
.animation(.default) /// add the animation
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With