直接设置选择而不是从 viewModel 设置选择时,SwiftUI NavigationLink 行为不同

SwiftUI NavigationLink behaviour is different when selection is set directly, rather than from viewModel

我一直在使用 SwiftUI 和 运行 遇到意外行为。

我有视图 A、视图 B 和视图 C。视图 C 具有从视图 A 更改 AppState 的 EnviromentObject

视图 B 有带选择的 ViewModel

如果我从 ViewModel 调用函数来更改选择,那么 视图 C 显示几秒钟,然后自动弹出回到视图 B

如果我直接从视图 B(而不是 ViewModel)更改选择,一切都会按预期进行。 另外,如果我注释掉 onDissapear,它也有效。但是,我需要在屏幕消失时更改 environmentObject 这是视图 B 和 ViewModel

import SwiftUI

class AppState: ObservableObject {
    @Published
    var shouldHideUserInfo = false
}


struct ContentView: View {
    
    @EnvironmentObject
    var appState: AppState
    
    @State
    var selection: Int? = nil
    
    var body: some View {
        NavigationView {
            VStack {
                if !appState.shouldHideUserInfo {
                    Text("USER INFO")
                }
                
                NavigationLink(
                    destination: ViewA(),
                    tag: 1,
                    selection: $selection,
                    label: { EmptyView()})
                
                Button("MOVE TO VIEW A") {
                    selection = 1
                }
            }
        }
    }
}


class ViewAModel: ObservableObject {
    @Published
    var selection: Int? = nil
    
    func navigate() {
        selection = 2 //<- this doesnt
    }
}

struct ViewA: View {
    
    @ObservedObject
    var viewModel: ViewAModel
    
    init() {
        viewModel = ViewAModel()
    }

    @State
    var selection: Int? = nil //<- this works
    
    var body: some View {
        VStack
        {
            Text("VIEW A")
            
            NavigationLink(
                destination: ViewB(),
                tag: 2,
                selection: $viewModel.selection,
                label: { EmptyView()})
            
            Button("MOVE TO VIEW B") {
                //selection = 2 <-- this works
                viewModel.navigate() //<- this doesnt
            }
           
        }
    }
}

struct ViewB: View {
    
    @EnvironmentObject
    var appState: AppState

    @State
    var selection: Int? = nil
    
    var body: some View {
        VStack
        {
            Text("VIEW B")
           
        }
        .onAppear {
            appState.shouldHideUserInfo = true
        }
    }
}

工厂模式没有解决问题:

    static func makeViewA(param: Int?) -> some View {
        let viewModel = ViewAModel(param: param)
        return ViewA(viewModel: viewModel)
    }
}

我明白了...它与 post 中的有点不同。问题是因为重新创建了视图模型(这是 NavigationView 长期观察到的行为),因此绑定丢失。

快速修复是

struct ViewA: View {
    
    @StateObject
    var viewModel: ViewAModel = ViewAModel()
    
    init() {
//        viewModel = ViewAModel()
    }

    // ... other code
}

备选方案是将视图模型的所有权保留在 ViewA 之外。

更新:与 SwiftUI 1.0 兼容 - 这是适用于任何地方的变体。问题的原因在 AppState 中。 ViewB 中的代码更新 appState

.onAppear {
    appState.shouldHideUserInfo = true
}

导致重建 ContentView 主体,重建 ViewA,重建 NavigationLink,丢弃之前的 link 和 ViewB 关闭。

为防止这种情况,我们需要避免重建 ViewA。这可以通过使 ViewA is-a Equatable 来完成,因此 SwiftUI 检查是否需要重新创建 ViewA,我们将回答否。

事情是这样的:

NavigationLink(
    destination: ViewA().equatable(),    // << here !!
    tag: 1,
    selection: $selection,
    label: { EmptyView()})

struct ViewA: View, Equatable {
    static func == (lhs: ViewA, rhs: ViewA) -> Bool {
        true
    }
    
    // .. other code