SwiftUI - IOS 16 NavigationStack - @StateObject init 被调用两次

Áko*_*vai 6 ios swift swiftui ios16 swiftui-navigationstack

描述

我正在尝试使我的应用程序适应 IOS 16 中引入的新功能。当我的一个视图中NavigationStack有一个变量时,我最终出现了奇怪的行为。@StateObject

当我导航(使用 new修饰符)到具有该对象的块.navigationDestination()的新视图时,该对象将运行两次。@StateObjectinit()

视图的主体如下所示:

VStack {
    Text("Param: \(intParam) and \(viewModel.someData)")
            
    Button("Do Something") {
        viewModel.buttonTapped()
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我删除 Button 元素,则init()只会@StateObject运行一次。

如果我使用旧NavigationLink(title:destination:)元素导航到新页面,它将运行一次@StateObject init()

完整代码

struct ContentView: View {
    
    var body: some View {
        NavigationStack {
            VStack {
                NavigationLink(value: 16) {
                    Text("Tap Me (IOS 16)")
                }
                NavigationLink("Tap Me (Older)") {
                    Page2(intParam: 45)
                }
            }
            .navigationDestination(for: Int.self) { i in
                Page2(intParam: i)
            }
            .navigationTitle("Navigation")
        }
    }
}

struct Page2: View {
    @StateObject var viewModel = ViewModel()
    let intParam: Int
    
    init(intParam: Int) {
        self.intParam = intParam
        print("Page2 view created")
    }
    
    var body: some View {
        VStack {
            Text("Param: \(intParam) and \(viewModel.someData)")
            
            Button("Do Something") {
                viewModel.buttonTapped()
            }
        }
    }
}

extension Page2 {
    @MainActor class ViewModel: ObservableObject {
        @Published var someData = "something"
        
        init() {
            print("Page2 viewmodel created")
        }
        
        func buttonTapped() {
            print("do something")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您知道这种行为的原因吗?

Bob*_* Xu 2

这不是一个错误,但它肯定会让开发人员感到困惑。当使用NavigationStack的destination方法导航到其他视图时,SwiftUI确实可能会多次调用navigationDestination内部的闭包代码(大部分是两次)。但是,它使用上次调用的结果来生成下一层视图。这意味着无论您创建多少个 StateObject 实例,只有最后生成的实例才会在目标视图中使用。其他实例将被自动销毁。

准确的说,这并不是创建同一个StateObject的多个实例,而是执行多次创建视图的操作,只保留最后一次创建的结果。

作为开发人员,我们只需要知道,当访问下一层视图时,您的 StateObject 实例是唯一的,并且在导航到更多层时仍然保持唯一。

这也从另一个角度告诉开发者不要在init中进行任何昂贵的操作,而是将此类操作放在onAppear或task中。

至于回复中提到的onAppear可能会被多次调用(返回视图时)的问题,可以通过添加@State标志来解决。