Updated the TaskListView.Content view in the app target to support user reordering.

This commit is contained in:
2026-02-11 16:00:40 +01:00
parent 0c8589db27
commit 1e3cdbdd3f
@@ -5,42 +5,82 @@ extension TaskListView {
/// Internal content view that manages the dynamic SwiftData query for tasks. /// Internal content view that manages the dynamic SwiftData query for tasks.
/// ///
/// This view is responsible for querying and displaying the list of tasks based on /// This view is responsible for querying and displaying the list of tasks based on the specified
/// the specified group and completion status filter. It exists as a separate view /// group and completion status filter. It exists as a separate view to enable dynamic query
/// to enable dynamic query updates when filter parameters change. /// updates when filter parameters change.
///
/// Task reordering is enabled based on the ``TodoGroup/canReorder`` property.
/// Groups that support reordering (``TodoGroup/today``, ``TodoGroup/upcoming``,
/// ``TodoGroup/overdue``) allow users to drag and drop tasks to change their order.
/// The ``TodoGroup/completed`` group does not support reordering as tasks are
/// displayed in chronological order by completion date.
struct Content: View { struct Content: View {
// MARK: Type aliases
/// Closure type for handling task reorder operations.
///
/// - Parameters:
/// - tasks: The current array of 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 // MARK: Properties
/// Query to fetch todos filtered by group and completion status. /// Query to fetch todos filtered by group and completion status.
/// ///
/// This query is initialized with a `FetchDescriptor` that applies predicates /// This query is initialized with a todo fetch descriptor that applies predicates
/// based on the group type and completion filter. SwiftData automatically /// based on the group type and completion filter. SwiftData automatically
/// monitors changes and updates the view when the underlying data changes. /// monitors changes and updates the view when the underlying data changes.
///
/// Tasks are sorted by their `order` property first, then by date-based criteria
/// specific to each group, enabling both manual and automatic ordering.
@Query @Query
private var tasks: [Todo] private var tasks: [Todo]
/// Indicates whether tasks in this view can be reordered through drag and drop.
///
/// This value is derived from the ``TodoGroup/canReorder`` property and
/// determines whether the `.onMove` modifier is applied to the task list.
private let canReorder: Bool
/// Closure called when tasks are reordered through drag and drop.
///
/// This closure is invoked by the `.onMove` modifier and is responsible for
/// updating the `order` property of affected tasks to reflect their new positions.
private let onReorder: ReorderClosure
// MARK: Initializers // MARK: Initializers
/// Creates a content view with a configured task query. /// Creates a content view with a configured task query and reorder handler.
/// ///
/// This initializer sets up the `@Query` with a fetch descriptor that filters /// This initializer sets up a query with a fetch descriptor that filters
/// tasks based on the specified group and completion status. When these parameters /// tasks based on the specified group and completion status. When these parameters
/// change, SwiftUI recreates the view with new values, causing the query to update. /// change, SwiftUI recreates the view with new values, causing the query to update.
/// ///
/// The reordering capability is automatically determined based on the
/// ``TodoGroup/canReorder`` property. For groups that support reordering,
/// the provided closure will be called when users drag and drop tasks to new positions.
///
/// - Parameters: /// - Parameters:
/// - group: The todo group category to filter by (today, upcoming, overdue, or completed). /// - 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`. /// - showCompleted: Whether to include completed tasks in the results. Only applies to groups other than `.completed`.
/// - onReorder: A closure called when tasks are reordered.
init( init(
group: TodoGroup, group: TodoGroup,
showCompleted: Bool showCompleted: Bool,
onReorder: @escaping ReorderClosure
) { ) {
_tasks = .init( self._tasks = .init(
FetchDescriptor<Todo>( FetchDescriptor<Todo>(
group: group, group: group,
showCompleted: showCompleted showCompleted: showCompleted
) )
) )
self.canReorder = group.canReorder
self.onReorder = onReorder
} }
// MARK: Body // MARK: Body
@@ -49,6 +89,12 @@ extension TaskListView {
/// ///
/// Displays either a content unavailable state or a list of tasks /// Displays either a content unavailable state or a list of tasks
/// based on whether any tasks match the current filters. /// based on whether any tasks match the current filters.
///
/// When tasks are present, displays them in a grouped list. The list supports
/// drag and drop reordering for groups where `canReorder` is true, allowing
/// users to manually prioritize tasks within the group. For groups where
/// reordering is disabled (such as completed tasks), the list is displayed
/// in read-only mode with chronological sorting.
var body: some View { var body: some View {
Group { Group {
if tasks.isEmpty { if tasks.isEmpty {
@@ -59,8 +105,18 @@ extension TaskListView {
} }
else { else {
List { List {
ForEach(tasks) { todo in if canReorder {
TaskListView.Item(todo) ForEach(tasks) {
TaskListView.Item($0)
}
.onMove { source, destination in
onReorder(tasks, source, destination)
}
}
else {
ForEach(tasks) {
TaskListView.Item($0)
}
} }
} }
.listStyle(.grouped) .listStyle(.grouped)
@@ -80,7 +136,9 @@ extension TaskListView {
TaskListView.Content( TaskListView.Content(
group: .today, group: .today,
showCompleted: .random() showCompleted: .random()
) ) { _, _, _ in
// On reorder closure
}
} }
#Preview( #Preview(
@@ -90,7 +148,9 @@ extension TaskListView {
TaskListView.Content( TaskListView.Content(
group: .today, group: .today,
showCompleted: false showCompleted: false
) ) { _, _, _ in
// On reorder closure
}
} }
#Preview( #Preview(
@@ -100,5 +160,19 @@ extension TaskListView {
TaskListView.Content( TaskListView.Content(
group: .today, group: .today,
showCompleted: true showCompleted: true
) ) { _, _, _ in
// On reorder closure
}
}
#Preview(
"Task list content when only completed tasks shown",
traits: .sampleTasks
) {
TaskListView.Content(
group: .completed,
showCompleted: .random()
) { _, _, _ in
// On reorder closure
}
} }