skip to Main Content

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

3

Answers


  1. 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.

    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
  3. 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))
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search