首页 > 解决方案 > 直接设置选择而不是从 viewModel 设置选择时,SwiftUI NavigationLink 行为不同

问题描述

我一直在使用 SwiftUI 并遇到了意外的行为。

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

视图 B 具有带有选择的 ViewModel

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

如果我直接从视图 B(而不是视图模型)更改选择,一切都会按预期工作。另外,如果我注释掉Dissapear,它也可以工作。但是,当屏幕消失时我需要更改 environmentObject 这是 View 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)
    }
}

标签: swiftuiswiftui-navigationlinkswiftui-navigationview

解决方案


我明白了……这与帖子中的有点不同。问题是因为视图模型被重新创建(这是长期观察到的行为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,删除先前的链接并ViewB关闭。

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

这是怎么回事:

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

推荐阅读