NavigationLink hides the Destination View, or causes infinite view updates

折月煮酒 提交于 2020-12-15 07:07:40

问题


Let us consider the situation when you have ContentView and DestinationView. Both of them depend on some shared data, that typically lies inside the @ObservedObject var viewModel, that you pass from parent to child either via @EnvironmentObject or directly inside init(). The DestinationView in this case wants to enrich the viewModel by fetching some additional content inside .onAppear.

In this case, when using NavigationLink you might encounter the situation when the DestinationView gets into an update loop when you fetching content, as it also updates the parent view and the whole structure is redrawn.

When using the List you explicitly set the row's ids and thus view is not changed, but if the NavigationLink is not in the list, it would update the whole view, resetting its state, and hiding the DestinationView.

The question is: how to make NavigationLink update/redraw only when needed?


回答1:


In SwiftUI the update mechanism compares View structs to find out whether they need to be updated, or not. I've tried many options, like making ViewModel Hashable, Equatable, and Identifiable, forcing it to only update when needed, but neither worked.

The only working solution, in this case, is making a NavigationLink wrapper, providing it with id for equality checks and using it instead.

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
        }
    }
}

Then in ContentView use it with .equatable()

NavigationLinkWrapper(id: self.viewModel.hashValue,
                   label: myOrdersLabel,
             destination: DestinationView(viewModel: self.viewModel)
).equatable()

Helpful tip:

If your ContentView also does some updates that would impact the DestinationView it's suitable to use LazyView to prevent Destination from re-initializing before it's even on the screen.

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

P.S: Apple seems to have fixed this issue in iOS14, so this is only iOS13 related issue.



来源:https://stackoverflow.com/questions/64121813/navigationlink-hides-the-destination-view-or-causes-infinite-view-updates

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!