I'm trying to create a button that displays a different images/text depending on the current state of a task.
I've found the following doesn't work:
Button {
taskViewModel.completeTask(id: task.id)
} label: {
if task.taskCompleted {
Image(systemName: "square")
.foregroundStyle(.black)
.padding(10)
.background(Color(.white), in: RoundedRectangle(cornerRadius: 10))
} else {
Image(systemName: "checkmark.square")
.foregroundStyle(.black)
.padding(10)
.background(Color(.white), in: RoundedRectangle(cornerRadius: 10))
}
}
But this does work:
if task.taskCompleted {
Button {
taskViewModel.completeTask(id: task.id)
} label: {
Image(systemName: "checkmark.square")
.foregroundStyle(.black)
.padding(10)
.background(Color(.white), in: RoundedRectangle(cornerRadius: 10))
}
} else {
Button {
taskViewModel.completeTask(id: task.id)
} label: {
Image(systemName: "square")
.foregroundStyle(.black)
.padding(10)
.background(Color(.white), in: RoundedRectangle(cornerRadius: 10))
}
}
The code that doesn't work is far cleaner!. Surely there is a better way to do this than what I've done here?
The full code that isn't working is:
func TasksView(completed: Bool)->some View {
LazyVStack(spacing: 20) {
if let tasks = taskViewModel.filteredTasks{
if tasks.isEmpty{
Text("No Tasks Today")
.offset(y: 100)
}else{
ForEach(tasks){ task in
if completed && task.taskCompleted || !task.taskCompleted {
HStack(alignment: .top, spacing: 20) {
VStack(spacing: 12) {
Circle()
.fill(taskViewModel.isCurrentHour(date: task.taskDate) ? .black : .clear)
.frame(width: 13, height: 13)
.background(
Circle()
.stroke(.black, lineWidth: 2)
.padding(-4)
)
.scaleEffect(taskViewModel.isCurrentHour(date: task.taskDate) ? 1.2 : 0.8)
Rectangle()
.fill(.black)
.frame(width: 3)
}
VStack {
HStack(alignment: .top, spacing: 10) {
VStack(alignment: .leading, spacing: 12) {
Text(task.taskTitle)
.font(.title2.bold())
Text(task.taskDescription)
.font(.callout)
.foregroundStyle(.secondary)
}
.headerLeading()
Text(task.taskDate.formatted(date: .omitted, time: .shortened))
}
HStack(spacing: 8) {
if taskViewModel.isCurrentHour(date: task.taskDate) {
HStack(spacing: 5) {
ForEach(1...3, id: \.self) { user in
Image(systemName: "person")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 25, height: 25)
}
}
.headerLeading()
}
Button{
taskViewModel.completeTask(id: task.id)
} label: {
Image(systemName: task.taskCompleted ? "checkmark.square" : "square")
.foregroundStyle(.black)
.padding(10)
.background(Color(.white), in: RoundedRectangle(cornerRadius: 10))
}
Button{
taskViewModel.deleteTask(id: task.id)
} label: {
Image(systemName: "bin.xmark")
.foregroundStyle(.black)
.padding(10)
.background(Color(.white), in: RoundedRectangle(cornerRadius: 10))
}
}
.padding(.top)
.headerTrailing()
}
.padding(taskViewModel.isCurrentHour(date: task.taskDate) ? 15 : 0)
.padding(.bottom, taskViewModel.isCurrentHour(date: task.taskDate) ? 0 : 10)
.headerLeading()
.background(
Color(.black)
.cornerRadius(20)
.opacity(taskViewModel.isCurrentHour(date: task.taskDate) ? 1 : 0)
)
.foregroundColor(taskViewModel.isCurrentHour(date: task.taskDate) ? .white : .black)
}.headerLeading()
}
}
}
}else{
ProgressView()
.offset(y: 100)
}
}
.padding()
.padding(.top)
.onChange(of: taskViewModel.currentDay) { newDate in
taskViewModel.filterTodaysTasks()
}
}
When I try to run this the following error is thrown by Xcode:
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
CodePudding user response:
In lieu of more details, generally the best way to adjust your view in the result of an object's property is to make the smallest adjustment necessary. In this case, you can limit the change to the systemName
of the image:
Button {
taskViewModel.completeTask(id: task.id)
} label: {
Image(systemName: task.taskCompleted ? "checkmark.square" : "square")
.foregroundStyle(.black)
.padding(10)
.background(Color(.white), in: RoundedRectangle(cornerRadius: 10))
}
What is more likely happening is that task
isn't signalling its change to the SwiftUI rendering system, so after completeTask
has run there's no indication that the view needs to be re-evaluated.
CodePudding user response:
To SwiftUI, the identity of the button hasn't changed in the first example. You could fix this by adding an explicit .id(…)
. For example:
Button {
taskViewModel.completeTask(id: task.id)
} label: {
if task.taskCompleted {
Image(systemName: "square")
.foregroundStyle(.black)
.padding(10)
.background(Color(.white), in: RoundedRectangle(cornerRadius: 10))
} else {
Image(systemName: "checkmark.square")
.foregroundStyle(.black)
.padding(10)
.background(Color(.white), in: RoundedRectangle(cornerRadius: 10))
}
}.id("\(task.id)-\(task.completed)")
As soon as the task's completed
changes, and SwiftUI is re-evaluating the UI, it will detect that the button has a different ID and will then re-render it with your new image.
CodePudding user response:
There is a better and a cleaner way to do it, based on the state of the task completed.
Button {
taskViewModel.completeTask(id: task.id)
} label: {
Image(systemName: task.taskCompleted ? "checkmark.square" : "square") // Change image based on state of the task completed
.foregroundStyle(.black)
.padding(10)
.background(Color(.white), in: RoundedRectangle(cornerRadius: 10))
}