Pull to Refresh is a widespread UX pattern on mobile. Drag a list down and load new items. You can find that in all social media applications and almost all other apps that display data download from servers.

To achieve this in previous versions of SwiftUI, you will have to use UIKit code, create your view, or third-party library. But SWiftUI 3 brings a native ViewModifier that changes adding Pull to Refresh into child's play. Let's dive into code.

To display Pull to Refresh, you have to add a refreshable modifier to scrollable view, most commonly List or Grid. The refreshable view modifier uses async/await syntax and handles dismissing of refresh indicator automatically.

struct ContentView: View {
    @State private var rows = 0

    var body: some View {
        List(0..<rows, id: \.self) { number in
            Text("row \(number)")
        }
        .refreshable {
            await Task.sleep(5_000_000_000) // wait 5s
            rows += 10
        }
    }
}

For this example, I'm using a Task.sleep that simply waits for a given number of nanoseconds to pass. Typically, you will perform time-consuming asynchronous (like downloading data from the network) operations here.

And that's it. This code sample will add full Pull to Refresh handling. But there is one more thing that is worth mentioning. SwiftUI 3 added a new modifier to download initial data for a given view. This modifier is called task and also expects the async/await syntax.

struct ContentView: View {
    @State private var rows = 0

    var body: some View {
        List(0..<rows, id: \.self) { number in
            Text("row \(number)")
        }
        .task {
            await Task.sleep(3_000_000_000) // wait 3s
            rows = 10
        }
        .refreshable {
            await Task.sleep(5_000_000_000) // wait 5s
            rows += 10
        }
    }
}
Xcode refreshable experiment

Want to test it yourself? Download this Xcode project.