Easy Debounce In SwiftUI
When you use a textfield to trigger an expensive operation on any input change you probably want to debounce it. A search field for example should show live suggestions, but triggering the search on every keypress would send too many requests to the server. A strategy in this case would be to wait for 500ms after the last keypress and then send the request. If any key was pressed during the 500ms, the delay should reset.
You might reach for a timer in other programming languages, but structured concurrency makes this almost trivial to implement in SwiftUI.
TextField("Search", text: $text)
.task(id: text) {
do {
try await Task.sleep(for: .milliseconds(500))
} catch {
// Ignore CancellationError
return
}
suggestions = await expensiveSearch(text)
}
.task is the star of the show here. According to the documentation for .task:
If the id value changes, SwiftUI cancels and restarts the task.
Instead of listening to keypresses or text changes, you can pass the search text as the task’s ID. Should the user change the search text, the previous task gets cancelled and a new one is started.
Inside the task you can implement the idea “wait 500ms before searching” just like that.
The only thing you need to keep in mind is to catch and ignore the CancellationError
that gets thrown by Task.sleep should it get cancelled.