I’m quite new to SwiftUI and I am building an application that breaks down tasks into subtasks using AI, but I’m having issues displaying said Subtasks. The current functionality I want is for the user to be able to tick every subtask that they complete and it persists throughout the application, I am using SwiftData for this. I have the basic UI for the checklist implemented but the isCompleted property just does not want to toggle.
The toggle() function produces the same results but including the mutating function helps me to debug this.
I included debug prints in the code to debug the changes to the variable.
Here is the code for the task and subtask class/struct:
@Model
class BlendedTask: Codable, Identifiable {
var id = UUID()
let name: String
var subtasks: [Subtask]
var task: UserTask {
return UserTask(id: id, name: name, duration: 3, startTime: nil, priority: .medium, imageURL: "tornado", details: nil, pomodoro: true, pomodoroCounter: 0, blended: true)
}
private enum CodingKeys: String, CodingKey {
case id, name, subtasks
}
// Initializers
init(name: String, subtasks: [Subtask]) {
self.name = name
self.subtasks = subtasks
}
init() {
self.id = UUID()
self.name = ""
self.subtasks = []
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
subtasks = try container.decode([Subtask].self, forKey: .subtasks)
}
}
// Subtask
struct Subtask: Codable, Identifiable, Hashable{
var id = UUID()
let name: String
var details: [Detail]
private enum CodingKeys: String, CodingKey {
case id, name, details
}
}
// Subtask details
struct Detail: Codable, Identifiable, Hashable {
var id = UUID()
var description: String
var isCompleted: Bool
init(description: String) {
self.description = description
self.isCompleted = false
}
mutating func toggleCompleted() {
print("-struct OLD: (self.isCompleted)")
self.isCompleted.toggle()
print("-struct NEW: (self.isCompleted)")
print("toggled for detail: (self.description)")
}
}
And here is the implementation in the view to show the details:
struct BlendedTaskDetailView: View {
@Environment(.dismiss) private var dismiss
@State private var isAnimated = false
@State var blendedTask: BlendedTask
var body: some View {
ZStack {
Color.BG.ignoresSafeArea()
VStack {
Text("Task: " + blendedTask.name)
//modifiers
List {
ForEach(Array($blendedTask.subtasks.enumerated()), id: .1.id) { index, $subtask in
SubtaskCell(subtask: $subtask, taskNo: index + 1)
}
.listRowBackground(Color.faPurple)
}
// Modifiers
}
.padding(.top, 30)
}
}
}
struct SubtaskCell: View {
@Binding var subtask: Subtask
let taskNo: Int
@State private var isAnimated = false
var body: some View {
VStack(alignment: .center) {
Text("Task (taskNo): (subtask.name)")
// modifiers
ForEach(subtask.details.indices, id: .self) { j in
DetailRow(detail: $subtask.details[j])
}
}
//modifiers
}
}
struct DetailRow: View {
@Binding var detail: Detail
@State private var isAnimated = false
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(detail.description)
}
Spacer()
Button(action: {
withAnimation {
print("-view OLD: (detail.isCompleted)")
detail.toggleCompleted()
print("-view NEW: (detail.isCompleted)")
}
}, label: {
Image(systemName: detail.isCompleted ? "checkmark.circle.fill" : "circle")
.contentTransition(.symbolEffect(.replace))
})
.frame(width: 35, height: 35)
.foregroundStyle(detail.isCompleted ? .green : .white)
}
//modifiers
}
}
Here is what the UI looks like:
This is what is printed when a button is pressed:
1