SwiftUI如何摆脱NavigationView中通过NavigationLink链接的视图的不断初始化?

Posted

技术标签:

【中文标题】SwiftUI如何摆脱NavigationView中通过NavigationLink链接的视图的不断初始化?【英文标题】:SwiftUI how to get rid of constant initialization of views linked through NavigationLink in NavigationView? 【发布时间】:2021-08-23 19:43:47 【问题描述】:

在下面的示例中,NavigationView 中通过 NavigationLink 链接的视图有 2 个:根 ContentView 和从属 ListView。

    struct ContentView: View 
    
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    @State private var counter = 0
    
    var body: some View 
        
        NavigationView
            
            VStack
                
                Text("counter: \(counter)")
                    .onReceive(timer)  _ in
                        counter += 1
                    
            
                NavigationLink(destination: ListView()) 
                    Text("List").padding()
                
                
            
            
        
        
    


struct ListView: View 
    
    let items = ["first", "second", "third"]
    
    init() 
        print(Date(), "init ListView")
    
    
    var body: some View 
        
        List
            
            ForEach(items, id: \.self)  item in
                NavigationLink(destination: ItemView(item: item)) 
                    Text(item).padding()
                
            
        
        
    
    

在 ContentView 中,counter 变量每秒都在变化,这会导致 ContentView 被重绘,ListView 被永久初始化。 ListView 被初始化,并且“init ListView”每秒显示一次,无论 ContentView 或 ListView 上当前是什么。 从数据模型的角度来看,ListView 不依赖于计数器和 ContentView 的状态。

在实际应用中,情况要复杂得多,成本也高得多。 ContentView 显示状态不断变化的 MapView,ListView 显示记录的路线列表。 ListView 初始化包含一些非常昂贵的逻辑。 不断地初始化 ListView 是需要时间的,虽然它真的只是在切换到 ListView 时才需要,而不是每次 ContentView 改变状态时才需要。

在摆脱 ListView 对 ContentView 状态变化的依赖的同时,让 NavigationView 工作的正确方法是什么?

【问题讨论】:

这能回答你的问题吗? ObervableObject being init multiple time, and not refreshing my view 这是预期行为,您可以通过使用自定义 LazyView 或提供的 Lazy Views 之一来缓解它 【参考方案1】:

正如 lorem ipsum 所说,内容的初始化和更新是 SwiftUI 工作方式的一部分,也是意料之中的。为了克服这个问题,只需创建一个处理计时器的新视图,这样只有你的最后一个会被更新。您的 ContentView 应该看起来更像:

struct ContentView: View 
    var body: some View 
        NavigationView
            VStack
                CounterView()
                NavigationLink(destination: ListView()) 
                    Text("List").padding()
                
            
        
    

还有你的 CounterView :

struct CounterView: View 
    @State private var counter = 0
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    var body: some View 
        Text("counter: \(counter)")
            .onReceive(timer)  _ in
                counter += 1
            
    

但是,如果您需要访问您的 Timer 变量,而不必更新每个单独的视图,您可以考虑通过简单地调用“Timer.secondsUpdater”来使单调可访问。

extension Timer 
    static let secondsUpdater = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

您的 CounterView 最终会像这样调用计时器:

struct CounterView: View 
    @State private var counter = 0

    var body: some View 
        Text("counter: \(counter)")
            .onReceive(Timer.secondsUpdater)  _ in
                counter += 1
            
    

希望对你有所帮助?

【讨论】:

非常感谢我自己几乎做出了这个决定,但你的回答非常清楚地描述了一切))

以上是关于SwiftUI如何摆脱NavigationView中通过NavigationLink链接的视图的不断初始化?的主要内容,如果未能解决你的问题,请参考以下文章

如何在启动时显示 NavigationView (SwiftUI) 的详细视图

SwiftUI:如何更新 NavigationView 详细信息屏幕?

如何在 SwiftUI 中隐藏 NavigationView Bar

如何使用 SwiftUI 消除嵌套 NavigationView 中的空间

如何从 SwiftUI 中的 NavigationView 中删除搜索栏?

如何在 SwiftUI 中将 TabView 与 NavigationView 一起使用?