Fix update List slow in SwiftUI

Tram Ho

If you have a list of SwiftUI with lots of rows, you may find updating very slow when sorting or filtering those rows – s code may take a second or two or if you have many items or you may lose a few minute. I’ll show you which code is causing the problem, then show you a line of SwiftUI code that fixes that error and finally the most important part: explain why the problem occurred so that you use the code properly.

Come to Xcode

Our problem code

Here is a brief SwiftUI code showing the problem:

I have a view with an attribute, which is an array of 600 integers from 1 to 600.

Inside the body there is a VStack with Button shuffles the items each click and List shows all items. Array shuffling is how we simulate you changing items, because it forces the list to update its rows.

If you run that code in the simulator, you’ll see the button and list of items, and if you keep clicking the button, you’ll see nothing happening – at least at first. If we wait a little longer, the updated lists are constantly changing. And if you press the button again, the same thing happens – in fact it happens every time you sort or filter the list.

Press it one last time, but this time I want you to open the Xcode’s debug navigation first so you can see CPU usage. When you press Shuffle, you will see the CPU maximizing the entire time while the items are being shuffled, so it’s not like the program is about to sleep.

one-line fix for slow SwiftUI lists

I will show you exactly why this is happening in a moment, but first I want to show you how to fix it. Back in Xcode, add this code to the list – not the items in the list, but the list itself.

Your code will look like this:

Now if you run the code again, you will see that you can press Shuffle as many times as you like and it will update immediately. If you press it quickly, you can see the CPU usage increase, but that is hardly surprising.

So that is the problem and that is how it is overcome. But I don’t want you to just copy the code into your project and hope for the best, because that doesn’t teach you anything and you’re missing out on the opportunity to learn more about how SwiftUI works.

What makes it slow?

Comment on how to fix one of our lines so we can return to the old code again, then go to the Product menu and select Profile. This will launch Instruments, Xcode’s integrated performance analysis tool.

There are a lot of ways tools can test our code, but the best option here is Time Profiler because it reports what our code did while it was running. Now press Record, this will cause the application to launch in the emulator while the Time Profiler is watching.

This top row is CPU usage and there’s a bit of a spike there when the app launches, but it’s stable to 0 because our app is only in standby mode. Now let’s see what happens when you switch to the emulator and press Shuffle. CPU usage spikes to its maximum and basically stays there for a few seconds while the list is being updated. So the CPU is being maximized, but that doesn’t tell us anything we don’t yet know. To see why it is maximized, we need to look in Time Profilers, and in particular we should look for the heaviest stack trace. This is a great feature in Instruments that tells us which piece of code takes the most time in our program, so if you’re looking for just one piece of code to optimize, this is usually a place. good to get started.

You can see that some things are white and others are gray; White code is our own code, while gray stuff is Apple’s frame code. You can see next to each method name a number that tells us how many milliseconds have been spent on each method and you can see that thousands of milliseconds have been used in PlatformViewChild.update (), rows milliseconds in ListCoreBatchUpdates.formUpdates () in computeRemoveAndInserts (), etc.

Finally, you go to CollectionChanges.formChanges and in my test, 4833 milliseconds were used there. Less than 2818 milliseconds in Collection.commonPrefix and less than 1433 milliseconds in this protocol witness to Collection.subcript.read. Between these three modes is a gap of 3.4 seconds and the work will continue to go down afterwards. 368/5000 So our heaviest stack trace – which one piece of code causes the most work – is telling us that Collection.formChanges is ultimately responsible for 4.8 seconds of work. And in the main Time Profiler output, you can see that the total CPU time on the whole process is only 5.17 seconds. That means calling Collection.formChanges is a big part of our work.

It’s a different list, promise!

Back to our code, you can see what the problem is. Our list shows all 600 strings in the item array and the array marked with @State . It’s the Swift property wrapper that allows us to change the value of our view, but that also means that when the array changes, the body properties will be reactivated – it will update the view. to reflect those changes.

The list itself hasn’t changed, but the contents of the list – rows from items – have changed, because they are now in a new order. So List decides it wants to see how the items have changed, so it goes through its original entries and new entries and finds out which ones have been deleted and have just been moved. This is a really neat feature, because it allows the list to dynamically change its changes and that’s why the List needs that id parameter – it needs to be able to identify every single row, so that It can know when they move.

The problem is, it is trying to compare 600 items with 600 other items and that is extremely slow. Worse, if we used 10,000 rows, the code would never end; It will take too much time.

Go ahead and skip our fix for this problem, which is to use the id () modifier with the new UUID. Every time you create a UUID, you get a unique sequence of letters and numbers – you can try it yourself on the terminal by calling the uuidgen command a few times.

Remember, SwiftUI calls the body every time the @State property changes, which means we will get a new UUID every time the array changes. That new UUID then goes into the id () modifier, which is used to uniquely identify the views. We told SwiftUI that this list has a unique identifier and that identifier is a new UUID every time.

That’s why our one-line fix works so well: we’re making SwiftUI think the list itself is different so they don’t try to look inside to find the difference.

Now, there’s a downside to using id () like this: you won’t get animation. Remember, we’re effectively telling SwiftUI the old list is gone and now there’s a new list, which means it won’t try to move rows around with animation. However, if your application freezes for 10 seconds because SwiftUI calculates the difference, losing animation is a small price to pay!

The article is referenced from https://www.hackingwithswift.com/articles/210/how-to-fix-slow-list-updates-in-swiftui Thank you for reading the article!

Share the news now

Source : Viblo