1
class TodoItem : Identifiable {
    
    var id: UUID
    
    var itemDescription: String
    var itemCreatedAt: Date
    
    var isComplete: Bool
    
    init(text: String){
        self.id = UUID()
        self.itemDescription = text
        self.itemCreatedAt = Date.now
        self.isComplete = false
    }
    
}

And a corresponding Repository Class

class TodosRepository : ObservableObject{
    
    @Published var todos: [TodoItem] = []
    
    var completedTodos: [TodoItem] {
        
        let completed = todos.filter({
            $0.isComplete == true
        })
        return completed
    }
    
    init()
    {
    }
    
    func addTodo(text: String)
    {
        let todo = TodoItem(text: text)
        
        todos.append(todo)
    }
}

Then I have a view that has an ObservedObject and shows the todos.

struct MainView: View {
    
    @ObservedObject var todosRepository: TodosRepository
    
    @State private var newTodoText: String = ""
    
    var body: some View {
        
        
        ZStack {
            
            Color(.darkGray)
                .ignoresSafeArea()
            
            VStack {
                
                HStack {
                    Text("Count: \(todosRepository.completedTodos.count)")
                        .font(.title)
                        .foregroundColor(.white)
                    
                    Spacer()
                }
                .padding(.bottom, 10)
                
                ForEach(todosRepository.todos){ todo in
                    TodoListItemView(todoItem: todo)
                        .foregroundColor(.white)
                        .padding(.bottom, 10)
                    
                    Divider()
                        .background(Color(.lightGray))
                }
                
                Spacer()
                
                HStack {
                    TextField("New Todo Entry", text: $newTodoText)
                        .padding(10)
                        .foregroundColor(.blue)
                        .background {
                            Color(.white)
                                .cornerRadius(10)
                        }
                    
                    Button("Add", action: {
                        todosRepository.addTodo(text: newTodoText)
                        print(todosRepository.todos)
                    })
                    .padding(10)
                    .foregroundColor(.white)
                    .background {
                        Color(.blue)
                            .cornerRadius(10)
                    }
                }
                
            }
            .padding()
            
        }
        
        
        
        
        
    }
}

The Item View looks like this with a Toggle Button

struct TodoListItemView: View {
    
    @State var todoItem: TodoItem
    
    var body: some View {
        

        HStack {
            VStack(alignment: .leading) {
                Text(todoItem.itemDescription)
                    .font(.headline)
                Text(todoItem.itemCreatedAt.debugDescription)
                    .fontWeight(.thin)
                    .font(.subheadline)
            }
            Spacer()
            
            Toggle(isOn: $todoItem.isComplete) {}
                .frame(width: 50)
                
        }
    }
}

I have a View (iOS App) that represents a TodoList which contains a List of Todos from a DataModel. The TodoItemListView has a Toggle Button linked to the isCompleted property of the Todoitem Class. I want the MainView first Textbox that shows how many completed Items I have to update when I toggle the button. Right now the Text in the MainView is not updated, I think somehow the computed Property for the completedTodos is not working properly but I am not sure, I am a beginner in Swift and iOS Development. However if I complete a few Todos and then add a new Todo to the List the Number is correctly updated. It just does not update when I press the Toggle. How can I update the "Completed Todos Number" as soon as I toggle some of the Items.

anb2022
  • 11
  • 2
  • 1
    Your model should be a struct, not a class. You should also use a binding in your ForEach and not State in the detail view. You may want to check out the Apple or Hacking with Swift SwiftUI tutorials – jnpdx Jun 09 '23 at 05:38
  • The UI doesn't get updated as the value change of computed property is not published, [this](https://stackoverflow.com/questions/58203531/an-equivalent-to-computed-properties-using-published-in-swift-combine) may help – vignesh Jun 09 '23 at 06:11

1 Answers1

0

To display ...how many completed Items I have to update when I toggle the button., use the following example code, where TodoItem is changed to a struct, using @Binding var todoItem: TodoItem in TodoListItemView and passing a binding from ForEach($todosRepository.todos){ $todo in ... in MainView.

// for testing
struct ContentView: View {
    @StateObject var todosRepository = TodosRepository()
    
    var body: some View {
        MainView(todosRepository: todosRepository)
    }
}

struct TodoItem : Identifiable {  // <-- here
    var id: UUID
    var itemDescription: String
    var itemCreatedAt: Date
    var isComplete: Bool
    
    init(text: String){
        self.id = UUID()
        self.itemDescription = text
        self.itemCreatedAt = Date.now
        self.isComplete = false
    }
}

class TodosRepository : ObservableObject{
    @Published var todos: [TodoItem] = []
    
    var completedTodos: [TodoItem] {
        let completed = todos.filter({
            $0.isComplete == true
        })
        return completed
    }
    
    init(){ }
    
    func addTodo(text: String) {
        let todo = TodoItem(text: text)
        todos.append(todo)
    }
}

struct MainView: View {
    @ObservedObject var todosRepository: TodosRepository
    @State private var newTodoText: String = ""
    
    var body: some View {
        ZStack {
            Color(.darkGray)
                .ignoresSafeArea()
            VStack {
                HStack {
                    Text("Count: \(todosRepository.completedTodos.count)")
                        .font(.title)
                        .foregroundColor(.white)
                    Spacer()
                }
                .padding(.bottom, 10)
                ForEach($todosRepository.todos){ $todo in   // <-- here
                    TodoListItemView(todoItem: $todo)  // <-- here
                        .foregroundColor(.white)
                        .padding(.bottom, 10)
                    Divider().background(Color(.lightGray))
                }
                Spacer()
                HStack {
                    TextField("New Todo Entry", text: $newTodoText)
                        .padding(10)
                        .foregroundColor(.blue)
                        .background {Color(.white).cornerRadius(10)}
                    Button("Add", action: {
                        todosRepository.addTodo(text: newTodoText)
                        print(todosRepository.todos)
                    })
                    .padding(10)
                    .foregroundColor(.white)
                    .background {Color(.blue).cornerRadius(10) }
                }
            }
            .padding()
        }
    }
}

struct TodoListItemView: View {
    @Binding var todoItem: TodoItem  // <-- here
    
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(todoItem.itemDescription)
                    .font(.headline)
                Text(todoItem.itemCreatedAt.debugDescription)
                    .fontWeight(.thin)
                    .font(.subheadline)
            }
            Spacer()
            Toggle(isOn: $todoItem.isComplete) {}
                .frame(width: 50)
        }
    }
}