ios - 从子视图设置 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)
}
}
结果如下:
解决方案
在模拟一个商店,其中调度改变了一个直接触发 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 工厂对象对这个通用主题进行其他变体。
推荐阅读
- javascript - React bootstrap Form.Control onClick 回调不起作用?
- linux - 不懂 Linux 命令“排序”
- scala - 我如何在 kantan 中创建自定义 CellEncoder
- youtube-data-api - 为什么我在 YouTube 数据 API v3 中收到 403 错误?
- parsing - BeautifulSoup - 所有href链接似乎都没有被提取
- javascript - 尝试使用反应路由器在反应中重定向
- c - ESC[2J 和 CLS 在编程上有何不同,我将如何使用 Windows API 在 C 中实现它?
- javascript - 如何使用 Mapbox 在单个页面中显示两个地图
- python-3.x - TensorFlow 中的多输入建模,带有生成器
- git - 在 GitHub 操作工作流中确定合并推送的源分支名称