7

I originally asked this question:

SwiftUI - Dynamic List filtering animation flies to right side when data source is empty

There, I had a List without sections. I was filtering them so that it only showed the rows that contained the text inside a TextField. The solution was to wrap everything inside the List in a Section.

Unfortunately, I now need to filter Sections. Here's my code:

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 = ""

    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 those that contain searchText
                    groups.filter { group in
                        searchText.isEmpty || group.groupName.localizedStandardContains(searchText)
                    }
                    
                ) { group in
                    Section(header: Text(group.groupName)) {
                        ForEach(group.people) { person in
                            Text(person.name)
                        }
                    }
                    
                }
            }
            .animation(.default) /// apply the animation
        }
    }
}

Result:

filtering sections causes them to fly away

I pass in a filtered array in the ForEach to determine the Sections. However, whenever that array changes, the List animates really weirdly. The Sections zoom/fly to the right side, and come back from the left when the array includes them again. How can I avoid this animation?

If I remove .animation(.default), it doesn't animate at all, as expected. But, I would still like an animation. Is there a way to fade the changes, or slide them instead?

2
  • @Yodagama I'm not applying a .transition anywhere... does List do that by default? Commented Dec 5, 2020 at 1:33
  • 1
    Yet another swiftui bug Commented Dec 6, 2020 at 20:37

1 Answer 1

2
+50

The solution is not using List. As long as you're not using selection and row deleting a ScrollView is basically the same.

If you want to style it a bit like the List that's also not that hard:

struct SearchAnimationExample: View {

    ...

    var body: some View {

        VStack {
            TextField("Search here", text: $searchText) /// text field
                .padding()
            
            ScrollView {
                VStack(spacing: 0) {
                    ForEach(
                        groups.filter { group in
                            searchText.isEmpty || group.groupName.localizedStandardContains(searchText)
                        }
                    ) { group in
                        Section(header: header(title: group.groupName)) {
                            ForEach(group.people) { person in
                                row(for: person)
                                Divider()
                            }
                        }
                        
                    }.transition(.opacity) // Set which transition you would like
                    
                    // Always full width
                    HStack { Spacer() }
                }
            }
            .animation(.default) 
        }
    }
    
    func header(title: String) -> some View {
        HStack {
            Text(title).font(.headline)
            Spacer()
        }
        .padding(.horizontal)
        .background(Color.gray.opacity(0.4))
    }
        
    func row(for person: Person) -> some View {
        HStack {
            Text(person.name)
            Spacer()
        }.padding()
    }
}

Looks practically the same as the default list:

enter image description here

Sign up to request clarification or add additional context in comments.

6 Comments

Ah nice! But you can't apply list styles, right? For example InsetGroupedListStyle
Also it doesn't seem to animate on iOS 13... video
Warning: ScrollView and HStack/VStack don't reuse views, which can cause memory issues and lag, so I don't recommend this as a replacement of a list with hundreds of rows. Ideally, if you want to avoid Lists altogether, you should use LazyHStacks and LazyVStacks (however, unfortunately, they are only available from iOS 14).
Oh that iOS 13 not animating is interesting. Never seen that happen. Yeah no list styles.
Weird to find this answer as accepted, since a VStack is NOT a List. No cell re-using, no deletion, etc. ... This is a workaround at its best, when you don't have many items, otherwise, for dynamic content, it is not the way to go. Original question stays unanswerd, as I have the same problem.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.