From 1debdcd32294071b734529eb3bcff34f709abb89 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Wed, 11 Feb 2026 17:34:06 +0100 Subject: [PATCH] Added support for todo deletion to the TaskListView view in the app target. --- .../Views/TaskList/TaskListContent.swift | 84 ++++++++++++++----- App/Sources/Views/TaskListView.swift | 18 +++- 2 files changed, 78 insertions(+), 24 deletions(-) diff --git a/App/Sources/Views/TaskList/TaskListContent.swift b/App/Sources/Views/TaskList/TaskListContent.swift index f918ad2..d093caa 100644 --- a/App/Sources/Views/TaskList/TaskListContent.swift +++ b/App/Sources/Views/TaskList/TaskListContent.swift @@ -18,17 +18,24 @@ extension TaskListView { // MARK: Type aliases + /// Closure type for handling task deletion operations. + /// + /// - Parameters: + /// - todos: The current array of todo tasks. + /// - offsets: The index set of tasks to delete. + typealias DeleteClosure = ([Todo], IndexSet) -> Void + /// Closure type for handling task reorder operations. /// /// - Parameters: - /// - tasks: The current array of tasks. + /// - todos: The current array of todo tasks. /// - source: The index set of items being moved. /// - destination: The destination index for the moved items. typealias ReorderClosure = ([Todo], IndexSet, Int) -> Void // MARK: Properties - /// Query to fetch todos filtered by group and completion status. + /// Query to fetch todo tasks filtered by group and completion status. /// /// This query is initialized with a todo fetch descriptor that applies predicates /// based on the group type and completion filter. SwiftData automatically @@ -37,7 +44,7 @@ extension TaskListView { /// Tasks are sorted by their `order` property first, then by date-based criteria /// specific to each group, enabling both manual and automatic ordering. @Query - private var tasks: [Todo] + private var todos: [Todo] /// Indicates whether tasks in this view can be reordered through drag and drop. /// @@ -45,6 +52,12 @@ extension TaskListView { /// determines whether the `.onMove` modifier is applied to the task list. private let canReorder: Bool + /// Closure called when tasks are deleted through swipe-to-delete. + /// + /// This closure is invoked by the `.onDelete` modifier and is responsible for + /// removing the selected tasks from the model context. + private let onDelete: DeleteClosure + /// Closure called when tasks are reordered through drag and drop. /// /// This closure is invoked by the `.onMove` modifier and is responsible for @@ -67,12 +80,14 @@ extension TaskListView { /// - group: The todo group category to filter by (today, upcoming, overdue, or completed). /// - showCompleted: Whether to include completed tasks in the results. Only applies to groups other than `.completed`. /// - onReorder: A closure called when tasks are reordered. + /// - onDelete: A closure called when tasks are deleted. init( group: TodoGroup, showCompleted: Bool, + onDelete: @escaping DeleteClosure, onReorder: @escaping ReorderClosure ) { - self._tasks = .init( + self._todos = .init( FetchDescriptor( group: group, showCompleted: showCompleted @@ -80,6 +95,7 @@ extension TaskListView { ) self.canReorder = group.canReorder + self.onDelete = onDelete self.onReorder = onReorder } @@ -97,7 +113,7 @@ extension TaskListView { /// in read-only mode with chronological sorting. var body: some View { Group { - if tasks.isEmpty { + if todos.isEmpty { ContentUnavailableView( "view.task-list-content.content-unavailable.title", systemImage: "checkmark.circle" @@ -105,19 +121,7 @@ extension TaskListView { } else { List { - if canReorder { - ForEach(tasks) { - TaskListView.Item($0) - } - .onMove { source, destination in - onReorder(tasks, source, destination) - } - } - else { - ForEach(tasks) { - TaskListView.Item($0) - } - } + items } .listStyle(.grouped) } @@ -127,6 +131,34 @@ extension TaskListView { } +// MARK: - Helpers + +private extension TaskListView.Content { + + // MARK: Computed + + /// A view that renders the task items with appropriate modifiers. + @ViewBuilder + var items: some View { + let items = ForEach(todos) { + TaskListView.Item($0) + } + .onDelete { offsets in + onDelete(todos, offsets) + } + + if canReorder { + items.onMove { source, destination in + onReorder(todos, source, destination) + } + } + else { + items + } + } + +} + // MARK: - Previews #Preview( @@ -136,7 +168,9 @@ extension TaskListView { TaskListView.Content( group: .today, showCompleted: .random() - ) { _, _, _ in + ) { _, _ in + // On delete closure + } onReorder: { _, _, _ in // On reorder closure } } @@ -148,7 +182,9 @@ extension TaskListView { TaskListView.Content( group: .today, showCompleted: false - ) { _, _, _ in + ) { _, _ in + // On delete closure + } onReorder: { _, _, _ in // On reorder closure } } @@ -160,7 +196,9 @@ extension TaskListView { TaskListView.Content( group: .today, showCompleted: true - ) { _, _, _ in + ) { _, _ in + // On delete closure + } onReorder: { _, _, _ in // On reorder closure } } @@ -172,7 +210,9 @@ extension TaskListView { TaskListView.Content( group: .completed, showCompleted: .random() - ) { _, _, _ in + ) { _, _ in + // On delete closure + } onReorder: { _, _, _ in // On reorder closure } } diff --git a/App/Sources/Views/TaskListView.swift b/App/Sources/Views/TaskListView.swift index 9794943..955ec32 100644 --- a/App/Sources/Views/TaskListView.swift +++ b/App/Sources/Views/TaskListView.swift @@ -9,6 +9,10 @@ struct TaskListView: View { // MARK: Properties + /// The SwiftData model context for performing data operations. + @Environment(\.modelContext) + private var modelContext + /// The view model managing the state and business logic for this view. /// /// This view model holds the group configuration and completion filter state. @@ -40,8 +44,18 @@ struct TaskListView: View { Content( group: viewModel.group, showCompleted: viewModel.showCompleted - ) { tasks, source, destination in - viewModel.reorderTasks(tasks, from: source, to: destination) + ) { tasks, offsets in + viewModel.deleteTasks( + tasks, + at: offsets, + from: modelContext + ) + } onReorder: { tasks, source, destination in + viewModel.reorderTasks( + tasks, + from: source, + to: destination + ) } .navigationTitle(viewModel.navigationTitle) .toolbar {