3

I have a List with displays the result of a dynamic FetchRequest.

The code works fine, but having a bigger result set (e.g. 3000), the list is built quite slowly when the dynamic predicate changed.

struct ItemList: View {
    @State var startsWith: String = "A"

    var body: some View {
        NavigationView {
            VStack {
                TextField("Startswith", text:$startsWith)
                FilterRequestList(filter: startsWith)
            }
            .navigationBarTitle("Tasks CD")
        } 
    }
} 


struct FilterRequestList: View {
    var fetchRequest: FetchRequest<Item>

    init(filter: String) {
        if filter == "" {
            fetchRequest = FetchRequest<Item>(entity: Item.entity(),
                                              sortDescriptors: [],
                                              predicate: nil)
        } else {
            fetchRequest = FetchRequest<Item>(entity: Item.entity(),
                                              sortDescriptors: [],
                                              predicate: NSPredicate(format: "title BEGINSWITH %@", filter))
        }
    }

    var body: some View {
        VStack {
            Text("Count: \(fetchRequest.wrappedValue.count)")
            List(fetchRequest.wrappedValue, id: \.self) { item in
                Text("\(item.title) ")
            }
        }
    }
}

Any Idea, how to improve that?

Update: What I discovered: The first List is quite fast, but if the startsWith State changes, the reload is very slow. I added

FilterRequestList(filter: startsWith)
    .onAppear(perform: { print("appear F") })
    .onDisappear(perform: { print("disappear F") })

and discovered, the the FilterRequestList is not disappearing and reappearing, when the filter changed.

Could that be the problem? How can a recreation be forced?

4
  • 1
    You could try the threshold approach to loading list items here: medium.com/better-programming/… Commented Jan 5, 2020 at 22:35
  • 4
    List does not build/draw more rows than appeared on screen, so I assume this is not due to List but because of fetching/filtering. I would recommend to use Instrument to find real bottle neck before fixing something. Commented Jan 6, 2020 at 5:53
  • I am unfortunately not very experienced with Instruments. Having a look at it, I did not notice a hint for the performance issue. What might be interesting: commenting out the List, makes it fast, so my guess was, that the list is causing the performance issue. Commented Jan 6, 2020 at 11:17
  • added an update Commented Jan 6, 2020 at 13:29

2 Answers 2

5

Thanks to Paul Hudson I found the solution for the problem. In "https://www.youtube.com/watch?v=h0SgafWwoh8" he explains it in detail.

You simply have to add the modifier

.id(UUID())

to the list.

The problem was, that swiftUI tries to detect any changes form the old list to the new one to animate the changes. With the modifier the old and the new list for swiftUI are not the same lists (because of the always changing id), so there is no need to detect the changes. Swift can simply create the new list very fast. The only downcast is, that therefore there is no animation.

struct ItemList: View {
    @State var startsWith: String = "A"

    var body: some View {
        NavigationView {
            VStack {
                TextField("Startswith", text:$startsWith)
                FilterRequestList(filter: startsWith)
            }
            .navigationBarTitle("Tasks CD")
        } 
    }
} 


struct FilterRequestList: View {
    var fetchRequest: FetchRequest<Item>

    init(filter: String) {
        if filter == "" {
            fetchRequest = FetchRequest<Item>(entity: Item.entity(),
                                              sortDescriptors: [],
                                              predicate: nil)
        } else {
            fetchRequest = FetchRequest<Item>(entity: Item.entity(),
                                              sortDescriptors: [],
                                              predicate: NSPredicate(format: "title BEGINSWITH %@", filter))
        }
    }

    var body: some View {
        VStack {
            Text("Count: \(fetchRequest.wrappedValue.count)")
            List(fetchRequest.wrappedValue, id: \.self) { item in
                Text("\(item.title) ")
            }
            .id(UUID())
        }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Oh my God, this also solves my year-long problem and hack-attempts at changing nsSortDescriptors backing a SwiftUI Table on macOS. In one simple line!
2

This line is a real game-changer:

.id(UUID())

swiftui

See how laggy it was before, with 2 complex animations: https://ibb.co/MkNJcz0

And how smooth it looks now: https://ibb.co/19fm7Fn

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.