0
0
iOS Swiftmobile~20 mins

Repository pattern in iOS Swift - Mini App: Build & Ship

Choose your learning style9 modes available
Build: User List
This screen shows a list of users fetched from a repository. The repository abstracts data fetching, so the UI does not know where data comes from.
Target UI
---------------------
|     User List      |
---------------------
| * Alice            |
| * Bob              |
| * Charlie          |
|                    |
| [Refresh]          |
---------------------
Create a UserRepository protocol with a method to fetch users asynchronously.
Implement a MockUserRepository that returns a fixed list of users after a short delay.
Build a SwiftUI view that uses the repository to load and display users in a list.
Add a Refresh button to reload the user list from the repository.
Show a loading indicator while fetching data.
Starter Code
iOS Swift
import SwiftUI

// TODO: Define User model here

// TODO: Define UserRepository protocol here

// TODO: Implement MockUserRepository here

struct UserListView: View {
    // TODO: Add @State properties for users and loading state
    
    // TODO: Add repository property
    
    var body: some View {
        NavigationView {
            VStack {
                // TODO: Show loading indicator or user list
                
                Button("Refresh") {
                    // TODO: Load users from repository
                }
                .padding()
            }
            .navigationTitle("User List")
        }
        .onAppear {
            // TODO: Load users when view appears
        }
    }
}

struct UserListView_Previews: PreviewProvider {
    static var previews: some View {
        UserListView()
    }
}
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Task 7
Task 8
Solution
iOS Swift
import SwiftUI

struct User: Identifiable {
    let id: Int
    let name: String
}

protocol UserRepository {
    func fetchUsers() async -> [User]
}

class MockUserRepository: UserRepository {
    func fetchUsers() async -> [User] {
        try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second delay
        return [
            User(id: 1, name: "Alice"),
            User(id: 2, name: "Bob"),
            User(id: 3, name: "Charlie")
        ]
    }
}

struct UserListView: View {
    @State private var users: [User] = []
    @State private var isLoading = false
    private let repository: UserRepository = MockUserRepository()

    var body: some View {
        NavigationView {
            VStack {
                if isLoading {
                    ProgressView("Loading...")
                        .padding()
                } else {
                    List(users) { user in
                        Text(user.name)
                    }
                }
                Button("Refresh") {
                    Task {
                        await loadUsers()
                    }
                }
                .padding()
            }
            .navigationTitle("User List")
        }
        .task {
            await loadUsers()
        }
    }

    private func loadUsers() async {
        isLoading = true
        users = await repository.fetchUsers()
        isLoading = false
    }
}

struct UserListView_Previews: PreviewProvider {
    static var previews: some View {
        UserListView()
    }
}

We start by defining a simple User struct with an id and name. This lets SwiftUI identify each user uniquely in the list.

The UserRepository protocol declares a method fetchUsers() that returns users asynchronously. This hides the data source details from the UI.

MockUserRepository implements this protocol and simulates a network delay using Task.sleep. It returns a fixed list of users.

In UserListView, we use @State properties to hold the users and loading state. The repository is a constant instance of MockUserRepository.

The UI shows a ProgressView while loading, and a List of user names when done. The Refresh button triggers reloading.

We use Swift concurrency with async and await to fetch users without blocking the UI. The .task modifier loads users when the view appears.

This pattern cleanly separates data fetching logic from UI code, making it easier to maintain and test.

Final Result
Completed Screen
---------------------
|     User List      |
---------------------
| * Alice            |
| * Bob              |
| * Charlie          |
|                    |
| [Refresh]          |
---------------------
When the screen appears, a loading spinner shows briefly, then the user list appears.
Tapping the Refresh button shows the spinner again and reloads the user list.
The list displays user names in a scrollable list.
Stretch Goal
Add error handling to the repository and show an alert if fetching users fails.
💡 Hint
Modify fetchUsers() to throw errors. Use try? or do-catch in loadUsers(). Show an Alert with .alert modifier in the view.