在 SwiftUI 分组表中按下编辑模式按钮之前不会删除行

Posted

技术标签:

【中文标题】在 SwiftUI 分组表中按下编辑模式按钮之前不会删除行【英文标题】:Row not deleted until Edit Mode button pressed in SwiftUI Grouped Table 【发布时间】:2019-08-23 13:45:58 【问题描述】:

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

为了尝试追踪错误:

数据源对象符合 Hashable、Identifiable 和 Equatable。

实现了一个简单的删除操作(即 删除@Published 属性中的第一个对象)

数据源/视图模型存储在@EnvironmentData 对象中

所以简单的问题是我做错了什么会导致 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$0.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
        .onDeleteindex 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())

【问题讨论】:

我只是在大声思考 - 我的删除工作正常。您的列表以某种方式连接到vm,即AppData,对吗? (您的代码相当密集。)但我看到的唯一onDelete 涉及闭包内的FolderView,并且似乎使用Project?还是Folder?我的建议是 (1) 获得一个真正简单的 List 并从您的模型中删除,然后 (2) 慢慢地将这些其他内容添加到您的模型(可能还有您的列表组)中以查看具体你的问题是什么。祝你好运! 感谢您的回复。在得到一个没有问题的平面列表之后,我实际上是一步一步地构建的。数据源(@Published 属性)按预期实时更新。但 UI 不是,直到用户单击完成。这是意想不到的行为。整个代码库都包含在上面,除了用于启动 ContentView 的场景委托调用。 【参考方案1】:

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

这里你有另一个解决方法。它基本上通过封装第二个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$0.folder == folder)  (project: Project) in
                            Text(project.title.uppercased())
                        .onDeleteindex in
                            self.vm.delete()
                        
                    
                
            
            .listStyle(GroupedListStyle())
            .navigationBarItems(trailing: EditButton())
        
    

【讨论】:

这完全有效,没有警告。接受的答案。发现并解决了错误(没有 hack。)没有其他解释,除了在内部 ForEach 循环中嵌套命名/封装的 View 结构会破坏可预测的 UI 更新 - 当尝试实现 .onMove 和 .onDelete 修饰符时。这是 SwiftUI 中的一个错误,有望尽快得到解决。非常感谢您解决这个问题。 很高兴它成功了!让我们希望它在 GM 之前得到解决;-) 干杯。 简单的演示项目在这里github.com/taskcruncher/ForEachBug.git【参考方案2】:

所以奇怪的是,@kontiki 的(有用的)解决方案完全是巧合。事实证明,只需将一个(未使用的)函数类型变量添加到 FolderView 作为 View 属性参数并使用该函数参数在 init 方法中设置一个状态/环境类型的包装变量就可以解决问题。这是莫名其妙的。

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

DOESN'T WORK(添加不设置包装状态属性的函数参数

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

DOESN'T WORK(添加非函数参数)

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

我提交了一份错误报告,因为(在我看来)这是出乎意料的行为。

【讨论】:

以上是关于在 SwiftUI 分组表中按下编辑模式按钮之前不会删除行的主要内容,如果未能解决你的问题,请参考以下文章

在 SwiftUI 中按下按钮时更改字符串的文本

检测在 SwiftUI 中点击的系统警报按钮

SwiftUI:当我按下按钮时如何打开(App-)电话设置?

检查pickerView SwiftUi中的选择

如何在python tkinter中按下按钮之前使窗口状态空闲?

在位置访问权限中按下允许按钮后如何执行操作?