Adding Pull-to-Refresh in SwiftUI with refreshable
Sometimes, it is necessary to provide users with the ability to update a list through gestures. On Apple platforms, it is common to use the “pull-to-refresh” gesture. This gesture involves pulling down on a view, usually a List
, to refresh the data.
To implement this behavior in SwiftUI, we use the refreshable(action:)
modifier. This modifier allows us to provide a closure that will be executed when the user performs the gesture.
Let’s see an example:
struct TaskListView: View {
private var todos: [TodoItem] = [
.init(content: "Clean house"),
.init(content: "Cook"),
.init(content: "Go to work")
]
var body: some View {
NavigationStack {
List {
ForEach(todos) { todoItem in
Text(todoItem.content)
}
}
.refreshable {
await refresh()
}
.navigationTitle("My Tasks")
}
}
private func refresh() async {
try? await Task.sleep(nanoseconds: 1_000_000)
}
}
This code presents a List
that displays a list of Text
elements. The refreshable
modifier is applied to this List
, and it contains a closure that executes the asynchronous task refresh
.
It is important to note that it is not necessary to wrap the asynchronous code with Task
, as the closure expected in refreshable
can be async
, as shown in its definition:
public func refreshable(
action: @escaping @Sendable () async -> Void
) -> some View
The refresh
Environment Variable
When we use refreshable
, the content of the closure is stored in an environment variable. Therefore, we can access it using the following code:
import SwiftUI
struct MyChildView: View {
@Environment(\.refresh) private var refreshFunction
var body: some View {
Button("Run") {
await refreshFunction()
}
}
}
It is crucial to remember that this is only possible if the view where we use the environment variable is a child view of the view where we defined refreshable
previously.
If you want to read the Spanish version of this article, you can find it here: https://asynclearn.com/blog/pull-to-refresh-swiftui/