You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中嵌套NavigationView与NavigationStack的实现及问题解决

SwiftUI导航栈与ViewModel管理问题

目标

实现从深层视图直接返回至功能根视图,视图层级如下:

  1. ContentView
    • Feature1View
    • Feature2View
      • Feature2_1View
        • Feature2_1_1View
          • Feature2_1_1_1View ← 需要直接返回至Feature2View

遇到的问题

  1. ViewModel初始化与传递问题
    Feature2View需要初始化多个ViewModel,并通过.environmentObject传递给子视图:

    • Feature2_1ViewModel
    • Feature2_1_1ViewModel
    • Feature2_1_1_1ViewModel
      使用.environmentObject是必需的,因为navigationDestination(for:,destination:)会多次调用View的init()方法,在视图内部初始化ObservableObject成本过高。
  2. 嵌套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()
    }
    
  3. 隐藏导航栏返回按钮后侧滑手势异常
    设置.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 00:19:53