I have StaticTaskListView which uses a TaskView to display a filtered array of tasks. I use the StaticTaskListView for the "Done" and "Upcoming" sections of my Task Management app for macOS, and they display the tasks that are checked (have the isCompleted value set to true) or unchecked (the opposite).
The TaskView:
struct TaskView: View {
@Binding var tasks: [Task]
@Binding var task: Task
@Binding var isStrikethrough: Bool
@State private var isEditingDueDate = false
var body: some View {
HStack {
Image(systemName: task.isCompleted ? "checkmark.square.fill" : "stop")
.onTapGesture {
withAnimation {
task.isCompleted.toggle()
print("task.isCompleted: \(task.isCompleted)")
}
}
VStack(alignment: .leading) {
if isStrikethrough {
Text(task.title)
.fixedSize(horizontal: false, vertical: true)
.strikethrough(task.isCompleted)
.onTapGesture {
isStrikethrough.toggle()
}
} else {
TextField("New Task", text: $task.title, axis: .vertical)
.fixedSize(horizontal: false, vertical: true)
.textFieldStyle(.plain)
.strikethrough(task.isCompleted)
}
}
Spacer()
Text("\(dueDateFormatted)")
.foregroundColor(.gray)
.font(.caption)
Button(action: {
// Action for showing popover for editing due date
isEditingDueDate.toggle()
}) {
Image(systemName: "calendar")
}
.popover(isPresented: $isEditingDueDate) {
CalendarPopover(task: $task, isEditingDueDate: $isEditingDueDate)
.frame(width: 250, height: 50) // Set popover size as needed
}
.buttonStyle(PlainButtonStyle())
}
}
private var dueDateFormatted: String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .short
return dateFormatter.string(from: task.dueDate)
}
}
As can be seen in the above code, the checkbox icon(Image(systemName: task.isCompleted ? "checkmark.square.fill" : "stop") toggles the isCompleted value of a task.
In my StaticTaskListView, I access the TaskView directly, yet for some reason the checkbox icon is not reacting to tap gestures and updating the isCompleted value accordingly, despite this function being controlled by the TaskView. Here is this StaticTaskListview:
struct StaticTaskListView: View {
let title: String
let tasks: [Task]
let toggleAction: (Task) -> Void
let deleteAction: (Task) -> Void
let isStrikethrough: Bool
var body: some View {
List {
ForEach(tasks) { task in
TaskView(tasks: .constant([task]), task: .constant(task), isStrikethrough: .constant(isStrikethrough))
}
.onDelete { indexSet in
if let firstIndex = indexSet.first {
let taskToDelete = tasks[firstIndex]
deleteAction(taskToDelete)
}
}
}
}
}
FOR CONTEXT
What my app looks like ("All" view):
On the sidebar, you can see the "Done" and "Upcoming" sections, which are the ones that use StaticTaskListView to display a filtered array of tasks.
What the "Done" section looks like:
The TaskListView, which is used for the "all" section and works fine (toggles isCompleted fine): is here for reference. It also uses the TaskView directly:
struct TaskListView: View {
@Environment(\.colorScheme) private var colorScheme
var onDelete: (Task) -> Void
let title: String
@Binding var tasks: [Task]
@Binding var isStrikethrough: Bool
var body: some View {
List {
ForEach($tasks) { $task in
TaskView(tasks: $tasks, task: $task, isStrikethrough: $isStrikethrough)
.contextMenu {
Button(action: {
// Handle task deletion here
deleteTask(task: task)
}) {
Text("Delete")
Image(systemName: "trash")
}
}
}
.onDelete { indexSet in
if let firstIndex = indexSet.first {
// Assume that indexSet has only one element for simplicity
let taskToDelete = tasks[firstIndex]
deleteTask(task: taskToDelete)
}
}
}
}
private func deleteTask(task: Task) {
// Adjust the logic to delete the task
if let index = tasks.firstIndex(where: { $0.id == task.id }) {
tasks.remove(at: index)
}
}
}
THE DESIRED FUNCTIONALITY
Below is the desired functionality, shown in a previous version of my app where this functionality was not broken:

IMPORTANT NOTE
The functionality seen in the GIF for the task disappearing and the status being updated globally is already handled in my app, hence it working in the previous version. The only thing that isn't working in my current version is that the checkbox is not reacting to clicks. In this previous version, I did not use TaskView directly in the StaticTaskListView and instead recreated all the TaskView elements in the StaticTaskListView, practically duplicating the file. This got tedious to update, hence using an instance of TaskView directly in StaticTaskListView in the current version.
THE PROBLEM, SUMMARISED Essentially, the only thing that needs to change, and the reason for this post, is fixing the unresponsive checkbox button, allowing it to react to clicks. If anyone can find the source of the problem, that's all I need.
I am new to Swift and to StackOverflow. I understand that my code likely doesn't make too much sense in some places, or that I misuse some features of SwiftUI. If any further clarification or context is required I will provide it.

The main problem is that you are not actually handling the state of each
Task. A@Bindingproperty will be observed by SwiftUI to carry out view changes as needed, but it isn't responsible for holding on to that state, which is the job of an@Stateproperty (among others, such as@ObservedObject). You're creatingBinding<Task>objects when you declare theTaskView, whereas you should be using the projected value of some state property.I suspect further up in your view hierarchy you do have some stateful property, and that's why your existing
TaskListViewworks. Those@Bindingproperties are observing state changes above.This overview of state management in SwiftUI is worth a read.
The main change you need to make is in
StaticTaskListView, where you declare the tasks as@State, or properly pass in state as you had in your old view.