在 SwiftUI Grouped Table 中按下编辑模式按钮后,行才会被删除

Row not deleted until Edit Mode button pressed in SwiftUI Grouped Table

我使用 ObservableObject 作为数据源在 SwiftUI 中实现了一个分组 table。嵌套的 ForEach 用于生成每个部分。 EditMode() 按钮切换该环境 属性。在编辑模式下,删除操作完成后,删除的行(意外地)保留在屏幕上。 (即使该对象已从数据源数组中删除。)当用户 returns 进入正常查看模式时,该对象迟早会从 table 中删除。

为了尝试追踪错误:

所以简单的问题是我做错了什么会导致 SwiftUI 不立即在一个非常简单的(我认为)分组(按部分)列表上反映 EditMode 中的删除操作?

struct ContentView: View {

    @EnvironmentObject var vm: AppData

    var body: some View {

        NavigationView {

            List {
                ForEach(vm.folderSource) { (folder: Folder)   in
                    return Section(header: Text(folder.title)) {
                        //this is where problem originates. When I drop in a new full-fledged View struct, UI updates stop working properly when .onDelete is called from this nested View
                        FolderView(folder: folder)
                    }
                }
            }.listStyle(GroupedListStyle())
                .navigationBarItems(trailing: EditButton())
        }
    }
}

struct FolderView: View {

    var folder: Folder

    @EnvironmentObject var vm: AppData


    var body: some View {
        //I'm using a dedicated View inside an outer ForEach loop to be able to access a data-source for each dynamic view.

        let associatedProjects = vm.projects.filter{[=11=].folder == folder}

        return ForEach(associatedProjects) { (project: Project) in
            Text(project.title.uppercased())
            // dumbed-down delete, to eliminate other possible issues preventing accurate Dynamic View updates
        }.onDelete{index in self.vm.delete()}
    }
}


//view model
class AppData: ObservableObject {



    let folderSource: [Folder]
    @Published var projects: [Project]

    func delete() {
        //dumbed-down static delete call to try to find ui bug
        self.projects.remove(at: 0)
        //
    }


    init() {
        let folders = [Folder(title: "folder1", displayOrder: 0), Folder(title: "folder2", displayOrder: 1), Folder(title: "folder3", displayOrder: 2)  ]

        self.folderSource = folders


        self.projects = {

            var tempArray = [Project]()
            tempArray.append(Project(title: "project 0", displayOrder: 0, folder: folders[0]  ))
            tempArray.append(Project(title: "project 1", displayOrder: 1, folder: folders[0]  ))
            tempArray.append(Project(title: "project 2", displayOrder: 2, folder: folders[0]  ))


            tempArray.append(Project(title: "project 3", displayOrder: 0, folder: folders[1]  ))
            tempArray.append(Project(title: "project 4", displayOrder: 1, folder: folders[1]  ))
            tempArray.append(Project(title: "project 5", displayOrder: 2, folder: folders[1]  ))


            tempArray.append(Project(title: "project 6", displayOrder: 0, folder: folders[2]  ))
            tempArray.append(Project(title: "project 7", displayOrder: 1, folder: folders[2]  ))
            tempArray.append(Project(title: "project 8", displayOrder: 2, folder: folders[2]  ))

            return tempArray
        }()

    }

}


//child entity many-to-one (Folder)
class Project: Hashable, Equatable, Identifiable {

    let id = UUID()
    let title: String
    let displayOrder: Int
    let folder: Folder

    init(title: String, displayOrder: Int, folder: Folder) {
        self.title = title
        self.displayOrder = displayOrder
        self.folder = folder
    }

    static func == (lhs: Project, rhs: Project) -> Bool {
        lhs.id == rhs.id

    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

//parent entity: Many Projects have one Folder
class Folder: Hashable, Equatable, Identifiable {

    let id = UUID()
    let title: String
    let displayOrder: Int


    init(title: String, displayOrder: Int) {
        self.title = title
        self.displayOrder = displayOrder
    }

    //make Equatable
    static func == (lhs: Folder, rhs: Folder) -> Bool {
        lhs.id == rhs.id
    }

    //make Hashable
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

并且在SceneDelegate.swift

 // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView().environmentObject(AppData())

我删除了我之前的回答,因为正如你所说,虽然它有效但纯属巧合。

这里还有另一个变通办法。它基本上通过 not 封装第二个 ForEach 来工作。到目前为止,我发现封装是规避某些错误的好工具。在这种情况下是相反的!

struct ContentView: View {

    @EnvironmentObject var vm: AppData

    var body: some View {

        NavigationView {

            List {
                ForEach(vm.folderSource) { (folder: Folder)   in
                    Section(header: Text(folder.title)) {
//                        FolderView(folder: folder)
                        ForEach(self.vm.projects.filter{[=10=].folder == folder}) { (project: Project) in
                            Text(project.title.uppercased())
                        }.onDelete{index in
                            self.vm.delete()
                        }
                    }
                }
            }
            .listStyle(GroupedListStyle())
            .navigationBarItems(trailing: EditButton())
        }
    }
}

所以在一个奇怪的转折中,@kontiki 的(有用的)解决方案纯属巧合。事实证明,只需将一个(未使用的)函数类型变量添加到 FolderView 作为 View 属性 参数 并使用该函数参数在 init 方法 [=24] 中设置一个 State/Environment-type 包装变量=] 解决了这个问题。这是莫名其妙的。

WORKS(添加设置包装状态的函数参数属性['vm'是AppData视图模型的变量名,符合ObservableObject。见上文。)

FolderView(folder: folder, onDelete: {self.vm.hello = "ui update bug goes away, even though this function not called"}) //function sets EnvironmentObject-type property

不起作用(添加不设置包装状态的函数参数 属性

FolderView(folder: folder, onDelete: {print("ui update bug still here")})

不起作用(添加非功能参数)

FolderView(folder: folder, unusedString: "ui update bug still here") 

我提交了错误报告,因为(在我看来)这是意外行为。