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

SwiftUI how to get rid of constant initialization of views linked through NavigationLink in NavigationView?

在下面的示例中,NavigationView 中有 2 个通过 NavigationLink 链接的视图:根 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 工作的正确方法是什么?

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

希望对您有所帮助