SwiftUI中嵌套NavigationView与NavigationStack的实现及问题解决
SwiftUI导航栈与ViewModel管理问题
目标
实现从深层视图直接返回至功能根视图,视图层级如下:
- ContentView
- Feature1View
- Feature2View
- Feature2_1View
- Feature2_1_1View
- Feature2_1_1_1View ← 需要直接返回至Feature2View
- Feature2_1_1View
- Feature2_1View
遇到的问题
ViewModel初始化与传递问题
Feature2View需要初始化多个ViewModel,并通过.environmentObject传递给子视图:- Feature2_1ViewModel
- Feature2_1_1ViewModel
- Feature2_1_1_1ViewModel
使用.environmentObject是必需的,因为navigationDestination(for:,destination:)会多次调用View的init()方法,在视图内部初始化ObservableObject成本过高。
嵌套NavigationStack的环境对象传递失效
嵌套NavigationStack(path: $path)无法传递.environmentObject,代码示例:// ContentView.swift NavigationStack { // Feature2View.swift NavigationStack(path: $path) { // Feature2_1View.swift ← 会抛出错误 } .environmentObject() }当前临时解决方案是顶部使用
NavigationView替代外层NavigationStack:// ContentView.swift NavigationView { // Feature2View.swift NavigationStack(path: $path) { // Feature2_1View.swift } .environmentObject() }隐藏导航栏返回按钮后侧滑手势异常
设置.navigationBarBackButtonHidden(true)后,侧滑返回手势失效,当前采用的修复代码:extension UINavigationController { /// 隐藏返回按钮时保留侧滑返回手势 override open func viewDidLoad() { super.viewDidLoad() print("\(type(of: self)) \(#function)") interactivePopGestureRecognizer?.delegate = nil } }但该方案存在两个问题:
- 从Feature2View返回至ContentView时,侧滑返回手势失效
- 从Feature2_1View侧滑返回至Feature2View后,再次尝试进入Feature2_1View会导致界面冻结
尝试的两种实现方案
方案1:Feature1 - 每个视图独立初始化ViewModel
在每个视图内部使用@StateObject初始化对应的ViewModel,比如Feature1View持有Feature1ViewModel,Feature1_1View持有Feature1_1ViewModel。
Feature1.swift
enum Feature1ViewRoute { case Feature1_1 } class Feature1ViewModel: ObservableObject { let id = UUID() @Published var departure: Station? init() { print("\(type(of: self)) \(#function) \(id.uuidString)") } deinit { print("\(type(of: self)) \(#function) \(id.uuidString)") } } struct Feature1View: View { @StateObject private var feature1ViewModel: Feature1ViewModel init(feature1ViewModel: Feature1ViewModel = Feature1ViewModel()) { self._feature1ViewModel = StateObject(wrappedValue: feature1ViewModel) print("\(type(of: self)) \(#function)") } var body: some View { VStack { Text("Feature1") Text("departure \(feature1ViewModel.departure?.name ?? "")") Button { feature1ViewModel.departure = Station(name: "Lebak Bulus Grab") } label: { Text("set departure") } NavigationLink(value: Feature1ViewRoute.Feature1_1) { Text("Go to Feature1_1") } } .navigationDestination(for: Feature1ViewRoute.self) { route in switch route { case .Feature1_1: Feature1_1View(feature1ViewModel: feature1ViewModel).navigationBarBackButtonHidden(true) } } } }
Feature1_1.swift
enum Feature1_1ViewRoute { case Feature1_1_1 } class Feature1_1ViewModel: ObservableObject { @Published var currentStation: Station? let id = UUID() init() { print("\(type(of: self)) \(#function) \(id.uuidString)") } deinit { print("\(type(of: self)) \(#function) \(id.uuidString)") } } struct Feature1_1View: View { @ObservedObject var feature1ViewModel: Feature1ViewModel @StateObject private var feature1_1ViewModel: Feature1_1ViewModel = Feature1_1ViewModel() init(feature1ViewModel: Feature1ViewModel = Feature1ViewModel()) { self.feature1ViewModel = feature1ViewModel print("\(type(of: self)) \(#function)") } var body: some View { VStack { Text("Feature1_1View") Text("departure \(feature1ViewModel.departure?.name ?? "")") Text("current station \(feature1_1ViewModel.currentStation?.name ?? "")") Button { feature1ViewModel.departure = Station(name: "Fatmawati Indomaret Grab") } label: { Text("set departure") } Button { feature1_1ViewModel.currentStation = Station(name: "Fatmawati Indomaret Grab") } label: { Text("set current station") } NavigationLink(value: Feature1_1ViewRoute.Feature1_1_1) { Text("Go to Feature1_1_1") } } .navigationDestination(for: Feature1_1ViewRoute.self) { route in Feature1_1_1View().navigationBarBackButtonHidden(true) } } }
方案2:Feature2 - 根视图统一初始化所有ViewModel
在Feature2View中使用@StateObject初始化所有需要的ViewModel,然后通过构造函数传递给子视图。
Feature2.swift
enum Feature2ViewRoute { case Feature2_1 case Feature2_2 } class Feature2ViewModel: ObservableObject { var id = UUID() @Published var departure: Station? init() { print("\(type(of: self)) \(#function) \(id.uuidString)") } deinit { print("\(type(of: self)) \(#function) \(id.uuidString)") } } struct Feature2View: View { @StateObject private var feature2ViewModel = Feature2ViewModel() @StateObject private var feature2_1ViewModel = Feature2_1ViewModel() @StateObject private var feature2_1_1ViewModel = Feature2_1_1ViewModel() @StateObject private var feature2_1_1_1ViewModel = Feature2_1_1_1ViewModel() init() { print("\(type(of: self)) \(#function)") } var body: some View { VStack { Text("Feature2") Text("departure \(feature2ViewModel.departure?.name ?? "")") Button { feature2ViewModel.departure = Station(name: "Lebak Bulus Grab") } label: { Text("set departure") } NavigationLink(value: Feature2ViewRoute.Feature2_1) { Text("go to Feature 2_1") } } .navigationDestination(for: Feature2ViewRoute.self) { value in Feature2_1View( feature2ViewModel: feature2ViewModel, feature2_1ViewModel: feature2_1ViewModel, feature2_1_1ViewModel: feature2_1_1ViewModel, feature2_1_1_1ViewModel: feature2_1_1_1ViewModel ) // .navigationBarBackButtonHidden(true) } } }
Feature2_1.swift
enum Feature2_1ViewRoute { case Feature2_1_1 } class Feature2_1ViewModel: ObservableObject { var id = UUID() @Published var currentStation: Station? init() { print("\(type(of: self)) \(#function) \(id.uuidString)") } deinit { print("\(type(of: self)) \(#function) \(id.uuidString)") } } struct Feature2_1View: View { @ObservedObject var feature2ViewModel: Feature2ViewModel @ObservedObject var feature2_1ViewModel: Feature2_1ViewModel @ObservedObject var feature2_1_1ViewModel: Feature2_1_1ViewModel @ObservedObject var feature2_1_1_1ViewModel: Feature2_1_1_1ViewModel init( feature2ViewModel: Feature2ViewModel = Feature2ViewModel(), feature2_1ViewModel: Feature2_1ViewModel = Feature2_1ViewModel(), feature2_1_1ViewModel: Feature2_1_1ViewModel = Feature2_1_1ViewModel(), feature2_1_1_1ViewModel: Feature2_1_1_1ViewModel = Feature2_1_1_1ViewModel() ) { self.feature2ViewModel = feature2ViewModel self.feature2_1ViewModel = feature2_1ViewModel self.feature2_1_1ViewModel = feature2_1_1ViewModel self.feature2_1_1_1ViewModel = feature2_1_1_1ViewModel print("\(type(of: self)) \(#function)") } var body: some View { VStack { Text("Feature2_1") Text("departure \(feature2ViewModel.departure?.name ?? "")") Text("current station \(feature2_1ViewModel.currentStation?.name ?? "")") Button { feature2_1ViewModel.currentStation = Station(name: "Fatmawati Indomaret Grab") } label: { Text("set current station") } NavigationLink(value: Feature2_1ViewRoute.Feature2_1_1) { Text("go to Feature 2_1_1") } } .navigationDestination(for: Feature2_1ViewRoute.self) { _ in Feature2_1_1View( feature2ViewModel: feature2ViewModel, feature2_1ViewModel: feature2_1ViewModel, feature2_1_1ViewModel: feature2_1_1ViewModel, feature2_1_1_1ViewModel: feature2_1_1_1ViewModel ) // .navigationBarBackButtonHidden(true) } } }
内容的提问来源于stack exchange,提问作者Jason Rich Darmawan
相关产品推荐
相关产品推荐

