You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中切换Todo项时如何实时更新完成计数

SwiftUI TodoList 已完成计数实时更新问题解决

问题根源

你遇到的问题核心在于:TodoItem是普通类,修改它的isComplete属性时,TodosRepository中@Published var todos不会发送更新通知——@Published仅监听数组本身的引用变更(比如添加、删除元素),数组内部元素的属性修改无法触发它的更新,因此MainView的已完成计数不会实时刷新。

解决方案

以下是两种可行的修复方案,你可以根据场景选择:

方案一:将TodoItem改为ObservableObject

让TodoItem遵循ObservableObject协议,并用@Published标记isComplete属性,这样属性变化时会自动通知视图更新。

修改TodoItem类

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

修改TodoListItemView

使用@ObservedObject接收todoItem,确保Toggle的变化能触发视图更新:

struct TodoListItemView: View {
    @ObservedObject 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)
        }
    }
}

调整MainView的ForEach

由于TodoItem现在是ObservableObject,需要用$绑定来确保仓库感知到变化:

ForEach($todosRepository.todos){ $todo in
    TodoListItemView(todoItem: todo)
        .foregroundColor(.white)
        .padding(.bottom, 10)
    
    Divider()
        .background(Color(.lightGray))
}

方案二:通过仓库统一处理状态修改(推荐MVVM模式)

将Todo的状态修改逻辑集中到TodosRepository,确保数据变更时触发视图更新,更符合MVVM的架构设计。

给TodosRepository添加状态修改方法

class TodosRepository : ObservableObject{
    @Published var todos: [TodoItem] = []
    
    var completedTodos: [TodoItem] {
        todos.filter({ $0.isComplete == true })
    }
    
    init() {}
    
    func addTodo(text: String) {
        let todo = TodoItem(text: text)
        todos.append(todo)
    }
    
    // 新增:切换Todo完成状态的方法
    func toggleTodoCompletion(_ todo: TodoItem) {
        guard let index = todos.firstIndex(where: { $0.id == todo.id }) else { return }
        // 替换数组元素触发@Published更新
        var updatedTodo = todo
        updatedTodo.isComplete.toggle()
        todos[index] = updatedTodo
        
        // 或者手动发送更新通知:
        // objectWillChange.send()
    }
}

修改TodoListItemView,使用回调传递事件

struct TodoListItemView: View {
    let todoItem: TodoItem
    let onToggle: () -> Void // 回调闭包,用于通知父视图切换状态
    
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(todoItem.itemDescription)
                    .font(.headline)
                Text(todoItem.itemCreatedAt.debugDescription)
                    .fontWeight(.thin)
                    .font(.subheadline)
            }
            Spacer()
            
            // 使用constant绑定,通过回调处理状态变更
            Toggle(isOn: .constant(todoItem.isComplete), action: onToggle)
                .frame(width: 50)
        }
    }
}

在MainView中传递回调

ForEach(todosRepository.todos){ todo in
    TodoListItemView(todoItem: todo, onToggle: {
        todosRepository.toggleTodoCompletion(todo)
    })
    .foregroundColor(.white)
    .padding(.bottom, 10)
    
    Divider()
        .background(Color(.lightGray))
}

方案对比

  • 方案一:实现简单,适合小型项目或快速原型开发,直接让模型属性变更通知视图。
  • 方案二:将数据修改逻辑集中管理,降低视图与模型的耦合,利于后续功能扩展和维护,更符合iOS开发的最佳实践。

内容的提问来源于stack exchange,提问作者anb2022

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 06:44:57