同一列表中的多个 ForEach 与具有一个 ForEach 的多个列表

Posted

技术标签:

【中文标题】同一列表中的多个 ForEach 与具有一个 ForEach 的多个列表【英文标题】:Multiple ForEach in same List vs. Multiple Lists with one ForEach 【发布时间】:2020-07-30 21:24:46 【问题描述】:

我是 ios 和 SwiftUI 的新手,正在制作一个“目标”应用程序。我引用的代码是一个简单的列表,在标记为完成的目标之上列出了不完整的目标。我在尝试将多个 ForEachs 放入同一个 List 时遇到了这个有趣的问题。

当我使用下面的代码时,点击一个不完整的目标(从第一个 foreach 开始)会将该目标移动到边界之下,但 GoalView() 构造函数不会重新运行,因此目标似乎仍然不完整,而且,它仍然保留了原来的闭包(goal.markComplete()),这会导致崩溃。值得注意的是today 是一个@ObservedObject,它是通过调用markComplete()markIncomplete() 来更新的

List 
    ForEach(today.incompleteGoals, id: \.id)  goal in
        GoalView(goal: goal, isCompleted: false)
            .onTapGesture  goal.markComplete() 
    
    Text("---Boundary---")
    ForEach(today.completedGoals, id: \.id)  goal in
        GoalView(goal: goal, isCompleted: true)
            .onTapGesture  goal.markIncomplete() 
    

但是,当我切换到使用两个堆叠列表(见下文)时,问题就消失了。点击完成一个目标会将其移至底部列表,然后GoalView() 构造函数运行,改变目标的外观和行为。

VStack 
    List 
        ForEach(today.incompleteGoals, id: \.id)  goal in
            GoalView(goal: goal, isCompleted: false)
                .onTapGesture  goal.markComplete() 
    
    Text("---Boundary---")
    List 
        ForEach(today.completedGoals, id: \.id)  goal in
            GoalView(goal: goal, isCompleted: true)
                .onTapGesture  goal.markIncomplete() 
    

导致底部实现在ForEach 闭包中重新运行GoalView() 而顶部实现的根本区别是什么?不是吗?我特别困惑为什么两种实现都将完成的目标移动到边界以下,尽管最上面的一个似乎没有执行附加到第二个ForEach 的闭包。任何解释/提示将不胜感激!

编辑:today(类型Day)和goal(类型Goal)都是NSManagedObjects / CoreData 实体。下面是我对completedGoals 的定义,从扩展到Day,它应该阐明两者是如何定义和相关的。每个Day 都有称为completedGoals_incompleteGoals_ 的关系,它们是与那一天相关联的无序目标集。

var completedGoals: Array<Goal> 
    get 
        let result = (completedGoals_ as? Set<Goal>) ?? []
        return Array(result).sorted(by: ...)
    
    set  completedGoals_ = Set(newValue) as NSSet  

这里是markComplete()Goal的扩展的粗略实现(daysThatDidntComplete_completedGoals_的反比关系,与不完整的关系相同):

func markComplete(on day: Day, context: NSManagedObjectContext) 
    // ...check & crash if self (goal) wasn't already incomplete on day
    removeFromDaysThatDidntComplete_(day)  // provided by CoreData
    addToDaysThatCompleted_(day)  // provided by CoreData
    try? context.save()

【问题讨论】:

你会显示今天的类型和目标的类型吗? 是的,两者都是 CoreData 实体——NSManagedObject。我用我添加的相关扩展编辑了原始帖子。 【参考方案1】:

代码对我来说是不可测试的,所以只是一个想法 - 试试下面的

List 
    ForEach(today.incompleteGoals, id: \.id)  goal in
        GoalView(goal: goal, isCompleted: false)
            .onTapGesture  
                goal.markComplete() 
                self.today.objectWillChange.send()     // << here !!
            
    
    Text("---Boundary---")
    ForEach(today.completedGoals, id: \.id)  goal in
        GoalView(goal: goal, isCompleted: true)
            .onTapGesture  
                goal.markIncomplete() 
                self.today.objectWillChange.send()     // << here !!
            
    

【讨论】:

不走运。与添加 target.objectWillChange().send() 相同(和两者)

以上是关于同一列表中的多个 ForEach 与具有一个 ForEach 的多个列表的主要内容,如果未能解决你的问题,请参考以下文章

在 foreach 循环中切换语句 - 没有得到预期的结果

重载与重写

for 与forEach的区别

具有多个附加到列表的主键的数据库

重载与重写的区别

SwiftUI Picker 填充问题 - 选择器中的 ForEach 循环不填充