SwiftUI List performane

I have a performance issue with List when I have a large amount of data that is replaced. Given the code below, a data set of about 3500 name items are loaded from a fetch request. Depending of the selected gender in the segmented picker these items are filtered and the displayed in the List. When the List first render I have no performance issue with the loading and rendering of items. It scrolls nicely and smoothly through 1700 items. But as soon as I switch gender through the segemented picker it takes about 30-45 seconds to render the List again.


I think this has to do with removing 1700 items and the inserting 1500 items again from the List. Is there a best practice how to reload a large amount of items in SwiftUI? Or can I reset the List before I load it again, since there is no issue initially.


Anyone else having issue same issue?


struct NameList: View {
    
    @ObservedObject fileprivate var global = GlobalSettings()
    
    @FetchRequest(
        entity: Name.entity(),
        sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)]
    ) var names: FetchedResults<Name>
    
    @State var selectedGender = Defaults.gender
    
    var body: some View {
        
        let filtered = names.filter { $0.gender == self.selectedGender }
        
        return NavigationView {
            
            VStack {
                
                Picker("Gender", selection: $global.gender) {

                    Text(Gender.female.rawValue.uppercased())
                        .tag(Gender.female)

                    Text(Gender.male.rawValue.uppercased())
                        .tag(Gender.male)

                    Text(Gender.unisex.rawValue.uppercased())
                        .tag(Gender.unisex)

                }
                .pickerStyle(SegmentedPickerStyle())
                .padding()
                
                List( filtered, id: \.self) { (item: Name) in
                   NameListRow(item: item)
                }
            }
            
        }
        
        .onReceive(Defaults.publisher(for: \.gender)) { (gender) in
            self.selectedGender = gender
        }
        
    }
    
}
Answered by Seitenwerk in 387321022

My guess is your problem is that you are trying to filter the list directly or the array you pass to the list. Which leads to to whole thing being a big performance hog. Maybe try what Apple themselves showed in their WWDC videos: do not pass your array to „List(array){}“, instead create a List without passing anything „List{}“ and then inside the brackets do a for each with your array, where yo decide via classic SwiftUI „if“ when to render certain elements. This gives you multiple benefits and seems the be the recommended way in case you want to modify list content based on certain States. Apple used this method in their tutorials, to filter favorite items in a list if I remember correctly.


Update: Ok, I made small test yesterday and this approach also didnt work. Will keep an eye on this in case soem solution comes into my mind


Update 3: A collegue found a simple solution:

Instead of a complicated workaround, just empty the List array and then set the new filters array. It may be necessary to introduce a delay so that emptying the listArray won't be omitted by the followed write.

List(listArray){item in ... }


self.listArray = [] 
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
     self.listArray = newList
}

Thanks for your info, and I've already watched this video. However, this can only help for the List, right? My problem now is on a Detail List View. How to perfectly fix this issue? Anyone can help?

I know this question was answered a while ago but your problem is having the state value in the top level view and not the subview.

What you want:
  • Top level view - don't reload the List

  • NameListRow - Show or hide based on gender

Solution

Code Block
struct NameList: View {
@ObservedObject fileprivate var global = GlobalSettings()
@FetchRequest(
entity: Name.entity(),
sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)]
) var names: FetchedResults<Name>
var body: some View {
return NavigationView {
VStack {
Picker("Gender", selection: $global.gender) {
Text(Gender.female.rawValue.uppercased())
.tag(Gender.female)
Text(Gender.male.rawValue.uppercased())
.tag(Gender.male)
Text(Gender.unisex.rawValue.uppercased())
.tag(Gender.unisex)
}
.pickerStyle(SegmentedPickerStyle())
.padding()
List( names, id: \.self) { (item: Name) in
NameListRow(item: item)
}
}
}
}
}
struct NameListRow: View {
@State var selectedGender = Defaults.gender
var item: Item
var body: some View {
Group {
if self.selectedGener == item.gender {
content
}
}
.onReceive(Defaults.publisher(for: \.gender)) { (gender) in
self.selectedGender = gender
}
}
var content: some View {
*view logic*
}
}

SwiftUI List performane
 
 
Q