How to show progress on a task using ProgressView in SwiftUI
Jul 25, 2024In this tutorial, you will learn how to make use of ProgressView
in SwiftUI to show progress while the data of the View is fetched during an API call.
To make the tutorial more interesting, we'll be using an open jokes API. When the app is launched, the jokes will be fetched using the API and user will be able to refresh the jokes feed with the help of a refresh button.
This is how it is going to look like:
The idea here is to conditionally render the ProgressView
on the View when the API call is made and hide it as soon as we receive the response from the server. To achieve this, we will make use of a boolean @State
variable isFetching
.
func fetchJokes() {
isFetching = true
let url = URL(string: "https://official-joke-api.appspot.com/jokes/random/5")!
Task {
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decodedJokes = try JSONDecoder().decode([Joke].self, from: data)
DispatchQueue.main.async {
self.jokes = decodedJokes
self.isFetching = false
}
} catch {
print("Failed to fetch jokes: \(error)")
DispatchQueue.main.async {
self.isFetching = false
}
}
}
}
When the above function gets called, isFetching
variable will set to true and as a result, ProgressView
will be displayed on the view. Once the network request completes, the response will be either a success or failure. If the response is success, then the jokes array is updated with the new jokes and isFetching
is again set to false. Otherwise, we'll print the error and set isFetching
to false.
Now, let's build the UI for our app.
struct Joke: Identifiable, Hashable, Decodable {
var id: Int
var setup: String
var punchline: String
}
struct ContentView: View {
@State private var jokes: [Joke]?
@State private var isFetching: Bool = false
var body: some View {
VStack {
if isFetching {
ProgressView("Loading jokes for you")
.controlSize(.large)
} else {
if let jokes = jokes {
List(jokes) { joke in
VStack(spacing: 10) {
Text(joke.setup)
Text(joke.punchline)
}
.padding()
}
.listStyle(PlainListStyle())
} else {
Text("No jokes available.")
}
Spacer()
}
}
.onAppear {
fetchJokes()
}
}
}
Result:
In the above code:
- We first create a model for the joke that we want to display. It contains a subset of variables to be returned by the JSON object.
- We have created two state variables, one for the jokes array as it is going to get updated everytime the API is hit and the other one
isFetching
initialised to false. - To give more context about the ongoing task, a description is added to the
ProgressView
passed as a String. Also, size of theProgressView
has been increased using the.controlSize
modifier. - If the jokes are not fetched successfully, an alternate text "No jokes available" is displayed.
- Lastly, call
fetchJokes()
in theonAppear
modifier because as soon as the view appears this method should be called.
For the sake of simplicity, we are not following any architecture design like MVVM. If we were using the MVVM pattern then all the logic would go to ViewModel class.
Now, let's also create a Refresh button within the VStack
which calls the refreshJokes()
function.
Button(action: refreshJokes) {
Text("Refresh")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.padding()
The last step is to create the refreshJokes()
function which fetches new set of jokes everytime the user clicks on the Refresh button. For that, we will simply set the jokes array to nil and call the fetchJokes()
function again. Since, our API doesn't support pagination.
func refreshJokes() {
jokes = nil
fetchJokes()
}
Result:
If you want to style it better, you can follow the full source code available here.
Where to go next?
Today you learned you learned how to make make use of ProgressView
to indicate the user that a task is being performed in the background and the app is not stuck. As an iOS developer, ProgressView
is one of those components that used quite frequently in the application therefore, you should definitely have a good command on this concept.
To expand your knowledge about ProgressView
, we would highly recommend you to explore articles like Progress View in SwiftUI and How to create a Circular Progress Bar in SwiftUI.