I have a list, and I want to use swipeAction to either set or unset a property on the list items. My problem is that the function to decide if the property is set or unset is slow, and it runs not just when the user swipes (as I thought) but for every item as the list loads. Here is a mockup of the problem.
Note: In real life, the hasStar() function only takes 50ms, but the list is thousands of elements long, so the result is the same, a slow-loading list.
Is there a better way to do this?
struct ContentView: View {
let data = (1...10).map { "Item \($0)" }
var body: some View {
VStack {
List() {
ForEach(data, id: \.self) { item in
Text(item)
.swipeActions(edge: .leading) {
if hasStar() {
Button {
print("star removed")
} label: {
Label("Remove star", systemImage: "star.slash").font(.title)
}.tint(.red)
} else {
Button {
print("star added")
} label: {
Label("Add star", systemImage: "star").font(.title)
}.tint(.green)
}
}
}
}
}
}
func hasStar() -> Bool {
print("Slow function started")
sleep(1)
print("Slow function finished")
return true
}
}
The foreach inside the list prevents it from being lazily loading. Make the data list objects conform to Identifiable protocol. And use the List initializer.
See the official docs for a concert example - https://developer.apple.com/documentation/swiftui/list/
If I’m correct. Currently (Nov 2022), under the hood List is implemented using table view. Which loads and renders just the rows on screen. As such the function would be called only for the relevant row swiped and wouldn’t render the whole list.
Nevertheless, is hasStar is async. I’d suggest adding a loader until it finishes
Just to be clear the code should look like:
When using a big number of lines you would be able to see that only the visible part is shown and rendered (even when swipe)
Please note. This specific code example gets the ui stuck as it is sleeps the main thread. It is recommended to use asynchronous calls with some loader