在 SwiftUI 中,如何避免在从子视图更新共享状态数据时重绘父视图以避免导航问题?

use*_*594 6 xcode swift swiftui swiftui-navigationlink swiftui-navigationview

自 Xcode 12.5 以来,我看到很多“无法呈现。请提交错误”。console 登录 Xcode 控制台,我记得在 12.5 之前没有看到过。

当我NavigationLink从父视图导航到子视图,并且子视图中的逻辑更新父视图所依赖的状态之一时,会显示此消息。

下面是一个示例伪代码,其中消息列表显示在父视图中,消息详细信息显示在列表的子视图中,最后是独立设置子视图。

struct MessageListView: View {
    ...
    @StateObject var messageList = MessageList()

    var body: some View {
        debugPrint("!!MessageListView has been redrawn!!")
        return VStack {
            NavigationLink(destination:SettingsView()){
                Text("Go to settings")
            }
            ForEach(messageList.data.sorted(by: {$0.key < $1.key}), id:\.key) { k, m in
                NavigationLink(destination:MessageView(message:...){
                    Text(m.text)
                }
            }
        }
    }
}

struct MessageView: View {
    ...
    @ObservedObject var messageList : MessageList
    @ObservedObject var message : Message
    ...
    var body: some View {
        Text(...)
        .onAppear {
            messageList.readAndIncrement(message.id) //<- This updates both this view & parent view.
        }
    }
}

class MessageList : ObservableObject {
    @Published var data : [String:Message] = [:]

    func readAndIncrement(id: String){
        //Modify one of the message in dictionary.
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,当用户单击消息并遍历导航时,如下所示,

MessageListView -> MessageView
Run Code Online (Sandbox Code Playgroud)

一旦MessageView出现在屏幕上,由于 中的逻辑,它将增加消息的“读取计数” ,同时onAppear更新数据。MessageListView

一旦发生这种情况,MessageListView观察 MessageList 对象的父视图似乎就会更新,会发生以下两件事。

  1. debugPrint将在控制台上打印消息!!MessageListView has been redrawn!!(来自上面的源代码),这证明父视图已更新,而导航当前显示子视图。
  2. '无法呈现。请提交错误。控制台日志显示在屏幕上,可能是因为父视图中的更新“无法”显示在屏幕上?

因此,当我在子视图中查看和交互时更新父视图的观察数据时,SwiftUI 似乎抛出“无法呈现”错误日志,但我不确定如何正确修复以消除此错误。

我认为这不是一个错误,但我的错误是由于以下原因。

当用户进入完全不同的视图时,类似

MessageListView -> SettingsView
Run Code Online (Sandbox Code Playgroud)

并且如果满足以下两个条件,

  1. 子视图 (SettingsView) 不使用或依赖于任何父视图的状态对象。
  2. 父视图的数据模型通过其他方式更新,例如网络更新/同步、定期刷新等。

然后,父视图 (MessageListView) 会针对其依赖模型的每次更新进行重绘,并且子视图开始自行堆叠,如下所示。

在此输入图像描述

至少,在上述情况下,Unable to present. Please file a bug.不会显示在控制台中。

在 iOS 14.5 & Xcode 12.5补丁说明中,唯一相关的评论NavigationLink如下,但这似乎与我的情况无关。

仅本地状态不同的 NavigationLink 目标现在会在按预期在链接之间切换时重置该状态。(72117345)

所以我的问题是...在这种情况下如何正确管理状态(或解耦它们),并且不会导致 SwiftUI 导航中的时髦?

在屏幕上呈现子视图时更新父视图的状态数据是否违反 SwiftUI 的范例,或者这只是NavigationLinkXcode 12.5 中的一个错误?

use*_*594 2

我正在回答我自己关于我找到的解决方法的问题。对我有用的解决方案是使用Class而不是Struct更新Class. 这阻止了View正在监视@Published数组/列表的 被更新。

以前,我使用的是StructforMessage类型。最终发生的情况是,对结构/消息的属性之一的任何更改都会触发 UI 更新(这是预期的),即使不是View直接监视结构本身,而是监视结构列表(在我的例子中MessageList是类的属性) data.)。

Message从转换StructClass实现ObservableObject并更新其属性。当列表元素之一必须更新时,这可以防止依赖于消息列表的父视图被重绘。

总之,当您将@Published注释分配给 数组时Struct,列表项添加/删除以及对列表中结构项的属性之一的任何更改都将触发 SwiftUI 中的依赖视图更新。

另一方面,如果您将@Published注释分配给 数组Class,则只有列表项添加/删除才会触发 SwiftUI 更新。对类属性之一的任何更改都不会触发 SwiftUI 中的相关视图更新。