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

时间:2020-09-29 14:33:26

标签: swiftui ios13 swiftui-navigationlink

让我们考虑一下您拥有ContentViewDestinationView时的情况。两者都依赖于某些共享数据,这些共享数据通常位于@ObservedObject var viewModel内部,您可以通过@EnvironmentObject或直接在init()内部将其从父级传递到子级。 在这种情况下,DestinationView希望通过在.onAppear内部获取一些其他内容来丰富viewModel。

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

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

问题是:如何在需要时仅使NavigationLink更新/重绘

1 个答案:

答案 0 :(得分:0)

在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在屏幕上出现之前重新初始化。

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相关的问题。