NavigationLink 隐藏目标视图,或导致无限视图更新

NavigationLink hides the Destination View, or causes infinite view updates

让我们考虑一下您有 ContentViewDestinationView 的情况。它们都依赖于一些共享数据,这些数据通常位于 @ObservedObject var viewModel 中,您可以通过 @EnvironmentObject 或直接在 init() 中从父级传递给子级。 本例中的 DestinationView 想要通过在 .onAppear.

中获取一些额外的内容来丰富 viewModel

在这种情况下,当使用 NavigationLink 时,您可能会遇到 DestinationView 在获取内容时进入更新循环的情况,因为它还会更新父视图并且整个结构是重新绘制。

当使用 List 时,您显式设置了行的 ID,因此视图不会更改,但如果 NavigationLink 不在列表中,它将更新整个视图,重置其状态, 并隐藏 DestinationView.

问题是:如何在需要的时候NavigationLinkupdate/redraw

在 SwiftUI 中,更新机制会比较 View 结构,以确定它们是否需要更新。我尝试了很多选项,比如让 ViewModel HashableEquatableIdentifiable,强制它只在需要时更新,但都没有用。

在这种情况下,只有 工作解决方案是制作一个NavigationLink 包装器,为它提供id 以进行相等性检查并改为使用它。

struct NavigationLinkWrapper<DestinationView: View, LabelView: View>: View, Identifiable, Equatable {
    static func == (lhs: NavigationLinkWrapper, rhs: NavigationLinkWrapper) -> Bool {
        lhs.id == rhs.id
    }
    
    let id: Int
    let label: LabelView
    let destination: DestinationView // or LazyView<DestinationView>
    
    var body: some View {
        NavigationLink(destination: destination) {
            label
        }
    }
}

然后在 ContentView 中与 .equatable()

一起使用
NavigationLinkWrapper(id: self.viewModel.hashValue,
                   label: myOrdersLabel,
             destination: DestinationView(viewModel: self.viewModel)
).equatable()

有用提示:

如果您的 ContentView 也进行了一些会影响 DestinationView 的更新,则适合使用 LazyView 在 Destination 出现在屏幕上之前 re-initializing 阻止它。

struct LazyView<Content: View>: View {
    let build: () -> Content
    init(_ build: @autoclosure @escaping () -> Content) {
        self.build = build
    }
    var body: Content {
        build()
    }
}

P.S:Apple 似乎已经在 iOS14 中解决了这个问题,所以这只是 iOS13 相关的问题。