0
0
iOS Swiftmobile~20 mins

Structured concurrency in iOS Swift - Mini App: Build & Ship

Choose your learning style9 modes available
Build: User Data Loader
This screen loads user profile data and user posts concurrently using structured concurrency. It shows loading states and displays the data once both tasks complete.
Target UI
-------------------------
|      User Data Loader  |
-------------------------
| Loading user profile... |
| Loading user posts...   |
|                         |
| Name:                   |
| Email:                  |
|                         |
| Posts:                  |
| - Post 1                |
| - Post 2                |
|                         |
| [Reload Data]           |
-------------------------
Use Swift structured concurrency (async/await) to load user profile and posts concurrently.
Show separate loading indicators for profile and posts while loading.
Display user name and email after profile loads.
Display a list of post titles after posts load.
Add a Reload Data button to reload both concurrently.
Handle errors by showing an alert with a retry option.
Starter Code
iOS Swift
import SwiftUI

struct UserDataLoaderView: View {
    @State private var userName = ""
    @State private var userEmail = ""
    @State private var posts: [String] = []
    @State private var isLoadingProfile = false
    @State private var isLoadingPosts = false
    @State private var showError = false
    @State private var errorMessage = ""

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("User Data Loader")
                .font(.title)
                .padding(.bottom)

            if isLoadingProfile {
                Text("Loading user profile...")
            } else {
                Text("Name: \(userName)")
                Text("Email: \(userEmail)")
            }

            if isLoadingPosts {
                Text("Loading user posts...")
            } else {
                Text("Posts:")
                ForEach(posts, id: \.self) { post in
                    Text("- \(post)")
                }
            }

            Button("Reload Data") {
                // TODO: Add reload action
            }
            .padding(.top)
        }
        .padding()
        .alert(isPresented: $showError) {
            Alert(title: Text("Error"), message: Text(errorMessage), dismissButton: .default(Text("Retry")) {
                // TODO: Retry loading
            })
        }
        .onAppear {
            // TODO: Load data on appear
        }
    }

    // TODO: Add async functions to load profile and posts
}
Task 1
Task 2
Task 3
Task 4
Task 5
Solution
iOS Swift
import SwiftUI

struct UserDataLoaderView: View {
    @State private var userName = ""
    @State private var userEmail = ""
    @State private var posts: [String] = []
    @State private var isLoadingProfile = false
    @State private var isLoadingPosts = false
    @State private var showError = false
    @State private var errorMessage = ""

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("User Data Loader")
                .font(.title)
                .padding(.bottom)

            if isLoadingProfile {
                Text("Loading user profile...")
            } else {
                Text("Name: \(userName)")
                Text("Email: \(userEmail)")
            }

            if isLoadingPosts {
                Text("Loading user posts...")
            } else {
                Text("Posts:")
                ForEach(posts, id: \.self) { post in
                    Text("- \(post)")
                }
            }

            Button("Reload Data") {
                loadData()
            }
            .padding(.top)
        }
        .padding()
        .alert(isPresented: $showError) {
            Alert(title: Text("Error"), message: Text(errorMessage), dismissButton: .default(Text("Retry")) {
                loadData()
            })
        }
        .onAppear {
            loadData()
        }
    }

    func loadData() {
        isLoadingProfile = true
        isLoadingPosts = true
        showError = false

        Task {
            do {
                async let profile = fetchUserProfile()
                async let userPosts = fetchUserPosts()

                let (profileResult, postsResult) = try await (profile, userPosts)

                userName = profileResult.name
                userEmail = profileResult.email
                posts = postsResult

                isLoadingProfile = false
                isLoadingPosts = false
            } catch {
                isLoadingProfile = false
                isLoadingPosts = false
                errorMessage = error.localizedDescription
                showError = true
            }
        }
    }

    func fetchUserProfile() async throws -> (name: String, email: String) {
        try await Task.sleep(nanoseconds: 1_000_000_000) // 1 second delay
        if Bool.random() { // Simulate random error
            throw NSError(domain: "ProfileError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to load profile"])
        }
        return ("Jane Doe", "jane.doe@example.com")
    }

    func fetchUserPosts() async throws -> [String] {
        try await Task.sleep(nanoseconds: 1_500_000_000) // 1.5 seconds delay
        if Bool.random() { // Simulate random error
            throw NSError(domain: "PostsError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to load posts"])
        }
        return ["Post 1", "Post 2", "Post 3"]
    }
}

This solution uses Swift's structured concurrency with async let to start loading the user profile and posts at the same time. Both tasks run concurrently, making the app faster and more efficient.

Loading states isLoadingProfile and isLoadingPosts control the UI to show loading messages separately for profile and posts.

If either task throws an error, the catch block sets an error message and shows an alert with a retry button.

The loadData() function is called when the view appears and when the user taps the Reload Data button, allowing easy retry and refresh.

Simulated delays and random errors mimic real network calls for learning purposes.

Final Result
Completed Screen
-------------------------
|      User Data Loader  |
-------------------------
| Name: Jane Doe          |
| Email: jane.doe@example.com |
|                         |
| Posts:                  |
| - Post 1                |
| - Post 2                |
| - Post 3                |
|                         |
| [Reload Data]           |
-------------------------
When the screen appears, it shows loading messages for profile and posts.
After loading, it displays the user's name, email, and a list of posts.
If loading fails, an alert appears with an error message and a Retry button.
Tapping Reload Data restarts the loading process concurrently.
Stretch Goal
Add a pull-to-refresh gesture to reload the user data concurrently.
💡 Hint
Use SwiftUI's .refreshable modifier on the main VStack and call the loadData() function inside the refresh action.