0
0
iOS Swiftmobile~20 mins

Task and TaskGroup in iOS Swift - Mini App: Build & Ship

Choose your learning style9 modes available
Build: Task Manager
This screen shows a list of tasks grouped by categories. Each group has a header and tasks listed below. Users can see task names and their status (done or not).
Target UI
-------------------------
| Task Manager          |
-------------------------
| Work                 |
|  - Email client [ ]   |
|  - Prepare report [x] |
|-----------------------|
| Home                 |
|  - Clean kitchen [ ]  |
|  - Fix sink [ ]       |
-------------------------
Display tasks grouped by their category name as section headers
Each task shows its name and a checkbox indicating if it is done
Use Swift concurrency Task and TaskGroup to simulate loading tasks asynchronously
Show a loading indicator while tasks load
After loading, display the grouped tasks in a List
Starter Code
iOS Swift
import SwiftUI

struct TaskItem: Identifiable {
  let id = UUID()
  let name: String
  let done: Bool
}

struct TaskGroupData: Identifiable {
  let id = UUID()
  let category: String
  let tasks: [TaskItem]
}

struct TaskManagerView: View {
  @State private var taskGroups: [TaskGroupData] = []
  @State private var isLoading = true

  var body: some View {
    NavigationView {
      Group {
        if isLoading {
          ProgressView("Loading tasks...")
        } else {
          List {
            // TODO: Show grouped tasks here
          }
          .listStyle(GroupedListStyle())
        }
      }
      .navigationTitle("Task Manager")
      .task {
        // TODO: Load tasks asynchronously using TaskGroup
      }
    }
  }
}

struct TaskManagerView_Previews: PreviewProvider {
  static var previews: some View {
    TaskManagerView()
  }
}
Task 1
Task 2
Task 3
Task 4
Task 5
Solution
iOS Swift
import SwiftUI

struct TaskItem: Identifiable {
  let id = UUID()
  let name: String
  let done: Bool
}

struct TaskGroupData: Identifiable {
  let id = UUID()
  let category: String
  let tasks: [TaskItem]
}

struct TaskManagerView: View {
  @State private var taskGroups: [TaskGroupData] = []
  @State private var isLoading = true

  var body: some View {
    NavigationView {
      Group {
        if isLoading {
          ProgressView("Loading tasks...")
            .progressViewStyle(CircularProgressViewStyle())
        } else {
          List {
            ForEach(taskGroups) { group in
              Section(header: Text(group.category)) {
                ForEach(group.tasks) { task in
                  HStack {
                    Text(task.name)
                    Spacer()
                    Image(systemName: task.done ? "checkmark.square" : "square")
                      .foregroundColor(task.done ? .green : .secondary)
                      .accessibilityLabel(task.done ? "Done" : "Not done")
                  }
                  .padding(.vertical, 4)
                }
              }
            }
          }
          .listStyle(GroupedListStyle())
        }
      }
      .navigationTitle("Task Manager")
      .task {
        await loadTasks()
      }
    }
  }

  func loadTasks() async {
    isLoading = true
    var loadedGroups: [TaskGroupData] = []

    await withTaskGroup(of: TaskGroupData?.self) { group in
      let categories = ["Work", "Home"]

      for category in categories {
        group.addTask {
          // Simulate network delay
          try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second

          switch category {
          case "Work":
            let tasks = [
              TaskItem(name: "Email client", done: false),
              TaskItem(name: "Prepare report", done: true)
            ]
            return TaskGroupData(category: category, tasks: tasks)
          case "Home":
            let tasks = [
              TaskItem(name: "Clean kitchen", done: false),
              TaskItem(name: "Fix sink", done: false)
            ]
            return TaskGroupData(category: category, tasks: tasks)
          default:
            return nil
          }
        }
      }

      for await result in group {
        if let groupData = result {
          loadedGroups.append(groupData)
        }
      }
    }

    // Sort groups by category name
    taskGroups = loadedGroups.sorted { $0.category < $1.category }
    isLoading = false
  }
}

struct TaskManagerView_Previews: PreviewProvider {
  static var previews: some View {
    TaskManagerView()
  }
}

This solution uses Swift concurrency's withTaskGroup to load task groups asynchronously. Each category is loaded in its own child task with a simulated 1-second delay to mimic network loading.

After all tasks complete, the results are collected and sorted alphabetically by category. The UI shows a loading spinner while loading, then displays the tasks grouped by category in a List with sections.

Each task row shows the task name and a checkbox icon that is green if done, or gray if not. Accessibility labels describe the checkbox state for screen readers.

This approach teaches how to use TaskGroup to run multiple async tasks concurrently and update UI state when all finish.

Final Result
Completed Screen
-------------------------
| Task Manager          |
-------------------------
| Work                 |
|  - Email client [ ]   |
|  - Prepare report [x] |
|-----------------------|
| Home                 |
|  - Clean kitchen [ ]  |
|  - Fix sink [ ]       |
-------------------------
When the screen appears, a circular loading spinner with text 'Loading tasks...' shows for about 1 second.
Then the list of tasks grouped by 'Work' and 'Home' appears.
Each task shows a checkbox: checked for done tasks, empty for not done.
User can scroll the list if needed.
Stretch Goal
Add the ability to tap a task to toggle its done status in the UI.
💡 Hint
Use @State to track tasks and update the done property on tap. Use a Button or onTapGesture on each task row.