SwiftUI and Structured Concurrency
Learn how to manage concurrency into your SwiftUI iOS app. By Andrew Tetlaw.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
SwiftUI and Structured Concurrency
30 mins
- Getting Started
- Downloading a Photo
- Responding to Download Errors
- Using the Mars Rover API
- Fetching Rover Images
- Creating a Task
- Multitasking
- Defining Structured Concurrency
- Canceling Tasks
- Fetching All Rovers
- Creating a Task Group
- Exploring All Photos
- Displaying the Rover Manifests
- Presenting the Photos
- Browse by Earth Date
- Where to Go From Here?
Displaying the Rover Manifests
The solution is to drill down to photos by each Martian day — or sol — of a rover’s mission. The entries
property on PhotoManifest
contains only the data you need.
In the Views group, create a new SwiftUI View file called RoverManifestView.swift, and replace the default code with:
import SwiftUI
struct RoverManifestView: View {
// 1
let manifest: PhotoManifest
var body: some View {
// 2
List(manifest.entries, id: \.sol) { entry in
NavigationLink {
Text("Fear my botany powers, Mars!")
} label: {
// 3
HStack {
Text("Sol \(entry.sol)
(\(Text.marsDateText(dateString:
entry.earthDate)))")
Spacer()
Text("\(entry.totalPhotos) \(Image(systemName: "photo"))")
}
}
}
.navigationTitle(manifest.name)
}
}
Here’s what’s going on, step by step:
- Your new view needs a
PhotoManifest
to display. - You create a
List
by looping through each entry in the manifest. - Each entry creates a
NavigationLink
and shows the sol, the corresponding Earth date and the total photos for that day.
For completeness, add a suitable SwiftUI preview below RoverManifestView
:
struct RoverManifestView_Previews: PreviewProvider {
static var previews: some View {
RoverManifestView(manifest:
PhotoManifest(
name: "WALL-E",
landingDate: "2021-12-31",
launchDate: "2021-12-01",
status: "active",
maxSol: 31,
maxDate: "2022-01-31",
totalPhotos: 33,
entries: [
ManifestEntry(
sol: 1,
earthDate: "2022-01-01",
totalPhotos: 33,
cameras: []
)
]
)
)
}
}
This code will create a fictional manifest for the preview.
Finally, in RoversListView.swift, replace Text("I'm sorry Dave, I'm afraid I can't do that")
with:
RoverManifestView(manifest: manifest)
Build and run, select the Rovers tab, and drill down to each manifest. These rovers certainly are snap-happy Mars tourists!
So far, so good, but a step remains: displaying the photos.
Presenting the Photos
In the Views group, create another SwiftUI View file called RoverPhotosBySolListView.swift. Replace the default code with:
import SwiftUI
struct RoverPhotosBySolListView: View {
let name: String
let sol: Int
private let marsData = MarsData()
@State private var photos: [Photo] = []
var body: some View {
Text("Douglas Quaid, get to Mars!")
}
}
Your view has two arguments: the rover name and the sol. You also prepared the MarsData
and state properties you’ll need.
In RoverManifestView.swift, replace the Text
view in the NavigationLink
with:
RoverPhotosBySolListView(name: manifest.name, sol: entry.sol)
Next, in MarsData.swift, add a function to MarsData
that’ll download the photos by rover and sol:
func fetchPhotos(roverName: String, sol: Int) async -> [Photo] {
do {
return try await marsRoverAPI.photos(roverName: roverName, sol: sol)
} catch {
log.error("Error fetching rover photos: \(String(describing: error))")
return []
}
}
This simple async
function makes the appropriate API call and returns an array of Photo
.
In RoverPhotosBySolListView.swift, replace the Text
view with:
// 1
ZStack {
// 2
ScrollView {
LazyVStack(spacing: 0) {
ForEach(photos) { photo in
MarsImageView(photo: photo)
.padding(.horizontal, 20)
.padding(.vertical, 8)
}
}
}
// 3
if photos.isEmpty {
MarsProgressView()
}
}
// 4
.navigationTitle(name)
// 5
.task {
photos = await marsData.fetchPhotos(roverName: name, sol: sol)
}
That’s a lot of code! Here’s a breakdown:
- You set up a
ZStack
so theMarsProgressView
displays in the center while loading. - Then you present all the photos as a scrollable
LazyVStack
, usingMarsImageView
. - While
photos
is empty, you display the progress view. -
name
is a convenient navigation title. - Finally, your view’s task calls
fetchPhotos(roverName:sol:)
.
Build and run your app. Take some time to drill down and explore those photos. Consider how amazing it is to browse a photo album of pictures from the surface of another planet!
Browse by Earth Date
If you’re looking for an extra challenge, SwiftUI has an alternative to task(priority:_:)
called task(id:priority:_:)
. This alternative takes an Equatable
value representing an ID. When the ID value changes, the view cancels and restarts the task.
You could store the currently selected date in a state variable and use it for the ID. When the user selects a different date, the task will fetch the next photos. And yes, there’s an API endpoint for photos taken by a rover on a specific Earth date.
Where to Go From Here?
You can download the completed version of the project using Download Materials at the top or bottom of this tutorial.
In this tutorial, you learned how to manage concurrent tasks with async
, await
and TaskGroup
. Here are some links to help learn more about Swift Concurrency:
- The official Swift Concurrency page is a great resource as an overview.
- Get the Apple documentation straight from the source.
- raywenderlich.com offers a fantastic in-depth book on Swift Concurrency: Modern Concurrency In Swift.
We hope you’ve enjoyed this tutorial about structured concurrency and AsyncImage
in SwiftUI.
If you have any questions or comments, join the discussion below!