SwiftUI - IOS 16 导航栈 - @StateObject init 被调用两次

6

描述

我正在尝试将我的应用程序适配到 IOS 16 中引入的新的 NavigationStack。当我在一个视图中有一个@StateObject变量时,我遇到了奇怪的行为。

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

该视图的主体看起来像这样:

VStack {
    Text("Param: \(intParam) and \(viewModel.someData)")
            
    Button("Do Something") {
        viewModel.buttonTapped()
    }
}

但是

如果我删除按钮元素,@StateObjectinit()只会运行一次。

而且

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

完整代码

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

你有没有想过这种行为的原因?


“navigationDestination(for: destination:)” 被多次调用。您可以删除其余代码并尝试向 Apple 提交反馈。 - user1046037
非常有趣,我知道navigationDestination被多次调用,但从未见过它也在initing @StateObjects,这是因为body也被不必要地调用了。 我在论坛上发现了另一个已知问题,其中navigationDestination可能以错误的值被调用。https://developer.apple.com/forums/thread/714967 - malhal
那个也很有意思 @malhal。实际上,当ViewModel初始化时,我需要执行一系列操作。我需要启动与远程服务器的WebSocket通信,并且还需要启动一个计时器实例。但这样这些操作会被执行两次。我考虑在视图的 .onAppear() 中执行它们,但是每次返回到该页面时都会调用该方法。你有任何想法应该在哪里执行这些操作吗? - Ákos Morvai
你可能需要使用“延迟视图” - 我在这个相关的SO帖子中提供了一个:https://dev59.com/acXsa4cB1Zd3GeqPeGqB#75551258 - kwiknik
1个回答

2

这并不是一个 bug,但对于开发者来说可能会有些困惑。在使用 NavigationStack 的 destination 方法导航到其他视图时,SwiftUI 可能会多次调用 navigationDestination 内的闭包代码(通常是两次)。然而,它使用最后一次调用的结果生成下一层视图。这意味着无论您创建多少个 StateObject 实例,只有最后生成的实例将在目标视图中使用。其他实例将被自动销毁。

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

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

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

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


谢谢回复。我有一段时间没有处理这个问题了,但我会查看你的答案并接受它。 - Ákos Morvai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接