从子视图设置 StateObject 值会导致 NavigationView 弹出所有视图 [英] Setting a StateObject value from child view causes NavigationView to pop all views

查看:19
本文介绍了从子视图设置 StateObject 值会导致 NavigationView 弹出所有视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have an app that uses a StateObject in a redux-like pattern. It's been working fine - until I tried to implement a NavigationView with NavigationLinks that can be used programmatically.

每次我尝试发送调度"时来自子视图的操作,它将子视图从导航堆栈中弹出.我认为问题可能与我传递环境对象的位置有关,所以我将它从 NavigationView 移到了子视图.没有变化.

Every time I try to send a "dispatch" action from a child view, it pops the child off the navigation stack. I thought perhaps the problem had something to do with where I was passing the enviromentObject, so I moved it from the NavigationView to the child views. No change.

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

Why are my views popped immediately? Is there some redraw triggering that I'm unaware of?

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

结果如下:

推荐答案

在模拟 Store 中,dispatch 会改变直接触发 ObjectWillChange 的根 @Published 状态,我可以复制您的弹出行为.如果我通过使用 CurrentValueSubject 或嵌套 ObservableObject 将状态突变与 ObjectWillChange 分开,NavigationLink 将完全按预期执行.

In mocking up a Store where dispatches mutates a root @Published state that directly fires ObjectWillChange, I can replicate your popping behavior. If I separate state mutation from ObjectWillChange by using CurrentValueSubject or nesting an ObservableObject, NavigationLink performs exactly as desired.

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

I'd suggest decoupling a global root state firing from mandatorily marking all views as dirty. That way you can use a diffing mechanism to trigger that yourself.

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

That said, maybe there's a workaround re: @loremipsum comment. I am unfamiliar with that as I work with NavigationView in macOS multi-pane only, where I find it very reliable.

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

Given your Q in the comment, here is a rough demo that keeps your root state on an @Published property.

这让您可以将旧存储传递到环境中,并将其正常用于您可以对每个可能的状态突变进行差异化的视图.

That lets you pass your old store into the environment and use it as normal for views that you're OK with diffing for every possible state mutation.

模拟商店 &容器

Mock store & container

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 工厂对象的发布者.

The demo uses a view model object to partition updates, but you can use a Combine pipeline in a view, too. You may want to do other variations on this general theme, from the publisher you use for root state and a UI factory object.

这篇关于从子视图设置 StateObject 值会导致 NavigationView 弹出所有视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆