首页 > 解决方案 > 从子视图设置 StateObject 值会导致 NavigationView 弹出所有视图

问题描述

我有一个应用程序以类似 redux 的模式使用 StateObject。它一直运行良好 - 直到我尝试使用可以以编程方式使用的 NavigationLinks 实现 NavigationView。

每次我尝试从子视图发送“调度”操作时,都会将子视图从导航堆栈中弹出。我认为问题可能与我传递环境对象的位置有关,所以我将它从 NavigationView 移动到子视图。不用找了。

为什么我的观点立即弹出?是否有一些我不知道的重绘触发?

enum NavigationTag: String {
    case page1
    case page2
}

struct ContentView: View {
    @StateObject private var store = AppStore(
        initialState: .init(),
        reducer: appReducer)
    @State private var linkTag: NavigationTag? = .page1
    
    var body: some View {
        let splashNavView = NavigationView {
            VStack(alignment: .center) {
                Text("Loading..." + (linkTag?.rawValue ?? "nil"))
                Text("Something went wrong. You shouldn't be seeing this.")

                NavigationLink(destination: Page1View().environmentObject(store), tag: .page1, selection: $linkTag) {EmptyView()}
                NavigationLink(destination: Text("page 2 view").environmentObject(store), tag: .page2, selection: $linkTag) {EmptyView()}

            }
        }

        return splashNavView
        
    }
}

子视图

struct Page1View: View {
    @EnvironmentObject var store: AppStore
    
    var body: some View {
            ZStack {
                Color.orange
            }
            .onAppear(perform: {
                store.dispatch(.floatingView(action: .setSize(width: 414, height: 590)))
            })
            .navigationBarTitle("")
            .navigationBarHidden(true)
    }
}

结果如下:

标签: iosswiftswiftui

解决方案


在模拟一个商店,其中调度改变了一个直接触发 ObjectWillChange 的根 @Published 状态,我可以复制你的弹出行为。如果我通过使用 CurrentValueSubject 或嵌套 ObservableObject 将状态突变与 ObjectWillChange 分开,NavigationLink 将完全按照需要执行。

我建议将全局根状态触发与强制将所有视图标记为脏视图分离。这样你就可以使用差异机制来自己触发它。

也就是说,也许有一个解决方法:@loremipsum 评论。我不熟悉这一点,因为我只在 macOS 多窗格中使用 NavigationView,我发现它非常可靠。

编辑:示例

鉴于您在评论中的 Q,这是一个粗略的演示,它将您的根状态保持在 @Published 属性上。

这使您可以将旧存储传递到环境中,并正常使用它来处理您可以对每个可能的状态突变进行差异化的视图。

模拟商店和容器

class Store: ObservableObject {
    @Published var state = RootState()
}


struct RootState {
    var nav: NavTag = .page1
    var other: Int = 1
}

class Container: ObservableObject {
    let store = Store()

    func makeNavVM() -> NavVM {
        NavVM(store)
    }
}

struct RootView: View {
    
    @StateObject private var container = Container()
    
    var body: some View {
        ContentView(vm: container.makeNavVM())
            .environmentObject(container.store)
            .environmentObject(container)
    }
}

导航


class NavVM: ObservableObject {
    
    init(_ root: Store) {
        self.root = root
        root.$state
            .receive(on: DispatchQueue.main)
            .sink { [weak self] state in
                guard let self = self,
                      state.nav != self.tag else { return }
                self.tag = state.nav
            }
            .store(in: &subs)
    }
    
    @Published private(set) var tag: NavTag = .page1
    private var subs = Set<AnyCancellable>()
    private weak var root: Store?
    
    func navigate(to tag: NavTag?) {
        guard let tag = tag else { return }
        root?.state.nav = tag
    }
}

enum NavTag: String {
    case page1
    case page2
    
    var color: Color {
        switch self {
            case .page1: return .pink
            case .page2: return .yellow
        }
    }
    
    var label: String {
        switch self {
            case .page1: return "Page 1"
            case .page2: return "Page 2"
        }
    }
}

用法


struct ContentView: View {
    
    @StateObject var vm: NavVM

    var body: some View {
        NavigationView {
            VStack(alignment: .center) {
                Text("Your navigation view")
                NavigationButton(tag: .page1)
                NavigationButton(tag: .page2)

            }
        }
        .environmentObject(vm)
    }
}


struct Child: View {
    @EnvironmentObject var store: Store
    
    var color: Color
    var body: some View {
        VStack {
            Button { store.state.other += 1 } label: {
                ZStack {
                    color
                    Text("Press to mutate state \(store.state.other)")
                }
            }
            NavigationLink("Go to page not in NavTag or state", destination: Child(color: .green))
        }
    }
}


/// On its own the Link-in-own-view-encapsulation
/// worked on first screen w/o navigation,
/// but once navigated the view hierarchy will pop
/// unless you stop state updates at the top of the hierarchy
struct NavigationButton: View {
    @EnvironmentObject var navVM: NavVM
    var tag: NavTag
    
    var neveractuallynil: Binding<NavTag?> {
        Binding<NavTag?> { navVM.tag }
            set: {  navVM.navigate(to: $0) }
        }
    
    var body: some View {
        NavigationLink(destination: Child(color: tag.color),
                       tag: tag,
                       selection: neveractuallynil ) 
                { Text(tag.label) }
            
    }
}

该演示使用视图模型对象对更新进行分区,但您也可以在视图中使用组合管道。您可能希望从用于根状态的发布者和 UI 工厂对象对这个通用主题进行其他变体。


推荐阅读