Refreshable Modifier in SwiftUI

  • Feb 21, 2025

Refreshable Modifier in SwiftUI

  • DevTechie


At WWDC 2021, Apple introduced the refreshable modifier for SwiftUI. This new addition brings the "pull to refresh" functionality to SwiftUI, eliminating the need for UIKit bridging if you're targeting iOS 15 and above. This article will explore how to use the refreshable modifier, its syntax, and how to integrate it with network calls using modern Swift concurrency.



Introduction to the Refreshable Modifier

The refreshable modifier is a simple yet powerful tool that allows developers to add pull-to-refresh functionality to their SwiftUI views. It is available starting from iOS 15.0, macOS 12.0, tvOS 15.0, and watchOS 8.0. The modifier is straightforward to use, and it leverages Swift's new concurrency model with async and await.

Here’s the signature of the refreshable modifier:

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
public func refreshable(action: @escaping @Sendable () async -> Void) -> some View

This modifier marks the view as refreshable and provides a closure that allows you to fetch updates asynchronously. The closure is executed when the user triggers a refresh, and it can be used to update your data model.



Basic Usage of Refreshable Modifier

Let’s start with a simple example to demonstrate how to use the refreshable modifier. In this example, we’ll create a list that can be refreshed to add more items.

struct RefreshableExample: View {
    @State private var randomData = Array(0..<5)
    
    var body: some View {
        NavigationView {
            List {
                ForEach(randomData, id: \.self) { idx in
                    Text("Item #\(idx)")
                }
            }
            .refreshable {
                await fetchMoreData()
            }
            .navigationTitle("Refreshable List")
        }
    }
    
    private func fetchMoreData() async {
        // Simulate a network delay of 2 seconds
        try? await Task.sleep(nanoseconds: 2 * 1_000_000_000)
        randomData.append(Int.random(in: 10...1000))
    }
}

In this example, we have a List that displays a series of items. When the user pulls down to refresh, the fetchMoreData() function is called. This function simulates a network delay using Task.sleepand then appends a new random number to the randomData array.

Integrating with Network Calls

While the above example demonstrates the basic usage of the refreshable modifier, in real-world applications, you’ll likely want to fetch data from a remote API. Let’s enhance our example to include a network call using URLSession and Swift’s concurrency features.

We’ll use httpbin.org, a service that provides various endpoints for testing HTTP requests and responses. Specifically, we’ll use the /delay endpoint, which introduces a delay in the response to simulate a network call.

Here’s how you can update the fetchMoreData() function to include a network call:

private func fetchMoreData() async {
    do {
        // Simulate a network call with a 5-second delay
        let (_, _) = try await URLSession.shared.data(from: URL(string: "https://httpbin.org/delay/5")!)
        
        // Append a new random number to the list
        randomData.append(Int.random(in: 10...1000))
    } catch {
        print("Failed to fetch data: \(error)")
    }
}

In this updated function, we use URLSession.shared.data(from:) to make a network request. The await keyword ensures that the function waits for the network call to complete before proceeding. If the network call is successful, a new random number is appended to the randomData array. If an error occurs, it is caught and printed to the console.

Complete Example with Network Integration

Here’s the complete code for our RefreshableExample view, now integrated with a network call:

struct RefreshableExample: View {
    @State private var randomData = Array(0..<5)
    
    var body: some View {
        NavigationView {
            List {
                ForEach(randomData, id: \.self) { idx in
                    Text("Item #\(idx)")
                }
            }
            .refreshable {
                await fetchMoreData()
            }
            .navigationTitle("Refreshable List")
        }
    }
    
    private func fetchMoreData() async {
        do {
            // Simulate a network call with a 5-second delay
            let (_, _) = try await URLSession.shared.data(from: URL(string: "https://httpbin.org/delay/5")!)
            
            // Append a new random number to the list
            randomData.append(Int.random(in: 10...1000))
        } catch {
            print("Failed to fetch data: \(error)")
        }
    }
}

Key Points to Remember

  • Concurrency with async and await: The refreshable modifier works seamlessly with Swift’s concurrency model. You can use async functions to perform tasks like network requests, database queries, or any other asynchronous operations.

  • Error Handling: Always handle errors when making network calls or performing any operation that might fail. In the example above, we use a do-catch block to catch and handle any errors that occur during the network request.

  • User Experience: When integrating pull-to-refresh functionality, consider the user experience. For example, you might want to show a loading indicator or provide feedback if the refresh fails.

  • Availability: The refreshable modifier is available starting from iOS 15.0, macOS 12.0, tvOS 15.0, and watchOS 8.0. Ensure that your app’s deployment target is set accordingly.

Conclusion

The refreshable modifier in SwiftUI is a powerful and easy-to-use tool for adding pull-to-refresh functionality to your apps. By leveraging Swift’s concurrency features, you can seamlessly integrate asynchronous tasks like network requests into your refresh logic. Whether you’re fetching data from an API or simply updating a local data model, the refreshable modifier provides a modern and efficient way to keep your app’s content up-to-date.


Visit our website at https://www.devtechie.com for a wealth of additional learning resources.