【问题标题】:How to handle data deletions in SwiftUI (iOS) without crashing the app如何在 SwiftUI (iOS) 中处理数据删除而不会使应用程序崩溃
【发布时间】:2019-12-13 20:03:25
【问题描述】:

我有一个 SwiftUI 日历应用程序,其 UI 类似于内置的 Calendar.app。每当我尝试删除事件时,我都会崩溃。我的应用的整个生命周期如下:

  • 从服务器下载日历数据并填充模型([Events]、[Users]、[Responses] 等)
  • 将源数据转换为更结构化的格式(参见https://stackoverflow.com/a/58583601/2282313
  • 渲染事件列表视图,每个事件链接到详细视图和编辑模式(非常类似于 calendar.app)
  • 当一个事件被删除时,我告诉服务器删除该事件(如果是重复事件,服务器将删除多个事件),然后通过重新下载数据从服务器刷新我的数据,重新填充模型并重新生成结构化数据(这会导致列表刷新)。

执行此操作时,我的计算值会导致崩溃,因为详细视图中显示的事件数据不再可用。比如我得到一个用户的RSVP的数组索引如下:

    var responseIndex: Int {
        userData.responses.firstIndex(where: { $0.user == response.user && $0.occurrence == response.occurrence })!
    } 

我以为这是因为我在更新数据之前没有关闭显示已删除事件的视图,但即使我延迟数据刷新直到视图不再显示,我仍然会崩溃(SwiftUI 似乎保持这些视图在内存中)。

处理数据删除的正确方法是什么?我是否需要在我的 UserData EnvironmentObject 中保留已删除的事件并将它们标记为“已删除/隐藏”以避免此问题,还是有更好的方法来处理它?

这涉及到相当多的代码,所以提供一个示例很棘手,如果被问到我很乐意添加相关位。

【问题讨论】:

  • 我遇到了类似的问题,似乎没有任何效果。
  • @SybrSyn 我正在重写我的代码,这样我就不会删除任何用户数据,而是找到已删除的事件并将它们标记为这样。我还必须重新调整一些东西,以便我始终使用结构化数据作为事实来源(这很好!)。一旦我让它工作,我会分享我所做的,但可能有更好的方法来做,我还没有想出来。

标签: ios swift swiftui


【解决方案1】:

编辑:我发现这篇文章很好地澄清了一些事情:https://jasonzurita.com/swiftui-if-statement/

SwiftUI 非常乐意尝试渲染 nil 视图,它什么也没画。与直觉相反,避免崩溃并使编译器满意的一个好方法是围绕此设置您的代码。

原始“答案”如下...

我不知道这是否是执行此操作的“正确”方法,但我最终确保我的任何 UserData 都不会被删除以避免崩溃。我在我的 Occurrence(即事件)对象中添加了一个“已删除”布尔值,当我刷新我的结构化数据时,我从服务器获取最新数据,但检查是否有任何旧数据不再存在。步骤是:

  1. 从服务器获取最新的事件列表
  2. 为我的结构化数据创建第二个 init(),它将现有数据作为参数
  3. 在新的 init() 中,展平结构化数据,根据新数据检查已删除的项目,更新尚未删除的数据,剔除重复项,然后合并到新数据中。完成后,我用修改后的数据调用我原来的 init() 来创建新的结构化数据

代码如下:

init(occurrences: [Occurrence], existing: [Day]) {

    // Create a mutable copy of occurrences (useful so I can delete duplicates)
    var occurrences = occurrences

    // Flatten the structured data into a plan array of occurrences again
    var existingOccurrences = existing.compactMap({ $0.occurrences }).flatMap { $0 }

    // Go through existing occurrences and see if they still exist.
    existingOccurrences = existingOccurrences.map {
        occurrence -> Occurrence in
        let occurrenceIndex: Int? = occurrences.firstIndex(where: { $0.id == occurrence.id })
        // If the occurrence no longer exists, mark it as "deleted" in the original data
        if  occurrenceIndex == nil {
            var newOccurrence = occurrence
            newOccurrence.deleted = true
            return newOccurrence
            // If it still exists, replace the existing copy with the new copy
            // (in case it has changed since the last pull from the server)
            // Remove the event from the "new" data so you don't get duplicates
        } else {
            let newOccurrence = occurrences[occurrenceIndex!]
            occurrences.remove(at: occurrenceIndex!)
            return newOccurrence
        }
    }

    // Merge the existing data (with deleted items marked) and the updated data (with deleted items removed)
    let finalOccurrences = existingOccurrences + occurrences

    // Re-initialize the strutured data with the new array of data
    self = EventData(occurrences: finalOccurrences)
}

完成此操作后,我必须更新我的代码以确保我始终使用我的结构化数据作为事实来源(我以前没有这样做,因为访问“来源" 平面数据通常更容易,我更新了列表视图中的 ForEach 以仅在删除为 false 时才呈现一行。

有效!这可能是解决问题的次优方法,但不会再出现崩溃。仍然有兴趣了解解决问题的更好方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-03-06
    • 1970-01-01
    • 2015-05-18
    • 2016-08-08
    • 2021-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多