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

iOS16中带ObservedObject的自定义SwiftUI视图适配NavigationLink

iOS16 NavigationStack 传递ObservedObject ViewModel的导航实现

问题描述

我发现Hacking With Swift的示例不够全面,多数线上示例仅基于简单Hashable模型实现NavigationLink。我的自定义SwiftUI视图需要接收ObservedObject类型的ViewModel参数(比如ViewA(firstViewModel: self.firstViewModel)),且这些ViewModel在包含NavigationStack的根视图中实例化。在iOS16的新NavigationStack与NavigationLink编程式导航范式下,该如何实现向这类自定义视图的导航?此前iOS15中直接用NavigationLink(destination: someView(viewModel: self.viewModel)) { Text("Test Link") }即可实现,但现在的方式完全不同。我能否用枚举封装属性指向对应视图,同时保留正确的运行时ViewModel引用?

以下是最小复现代码:

struct ContentView: View {
    var body: some View {
        NavigationStack {
            ProfileView()
            // 其他根视图逻辑省略,问题聚焦在ProfileView的导航
        }
    }
}

struct ProfileView: View {
    @StateObject var progressViewModel = ProgressViewModel()
    @StateObject var challengesViewModel = ChallengesViewModel()
    @StateObject var subscriptionViewModel = SubscriptionViewModel() 
    var body: some View {
        VStack {
            // 下面的代码无法适配iOS16新导航模式,因为不是复用同一视图
            NavigationLink(destination: ProgressView(progressViewModel: self.progressViewModel)) {
                Text("用户进度导航")
            }
            
            NavigationLink(destination: ChallengesView(challengesViewModel: self.challengesViewModel)) {
                Text("挑战记录导航")
            }
            NavigationLink(destination: SubscriptionView(subscriptionViewModel: self.subscriptionViewModel)) {
                Text("订阅信息导航")
            }
        }
    }
}

struct ProgressView: View {
    @ObservedObject var progressViewModel: ProgressViewModel
    var body: some View {
        VStack {
            Text(progressViewModel.overallUserProgressString)
        }
    }
}

struct ChallengesView: View {
    @ObservedObject var challengesViewModel: ChallengesViewModel
    var body: some View {
        VStack {
            Text(challengesViewModel.numberOfChallengesPerformedString)
        }
    }
}

struct SubscriptionView: View {
    @ObservedObject var subscriptionViewModel: SubscriptionViewModel
    var body: some View {
        VStack {
            Text(subscriptionViewModel.localSubscriptionPriceString)
        }
    }
}

解决方案:用枚举封装导航路径 + 绑定NavigationPath

你可以通过定义导航路径枚举,结合NavigationPath和navigationDestination实现需求,同时保留对已实例化ViewModel的引用。具体步骤如下:

1. 定义导航路径枚举

创建枚举标识所有需要导航的目标视图,无需在枚举中存储ViewModel(ViewModel已在ProfileView中实例化):

enum ProfileNavigationDestination: Hashable {
    case progress
    case challenges
    case subscription
}

2. 修改ProfileView的导航逻辑

在ProfileView中添加@State var path = NavigationPath(),将NavigationStack的path绑定到该变量,替换原有的NavigationLink(destination:)为NavigationLink(value:),最后通过navigationDestination匹配枚举值并创建对应视图,传入已有的ViewModel:

struct ProfileView: View {
    @StateObject var progressViewModel = ProgressViewModel()
    @StateObject var challengesViewModel = ChallengesViewModel()
    @StateObject var subscriptionViewModel = SubscriptionViewModel()
    // 定义导航路径状态
    @State private var path = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $path) { // 绑定导航路径
            VStack {
                // 使用value参数指定导航目标枚举
                NavigationLink(value: ProfileNavigationDestination.progress) {
                    Text("用户进度导航")
                }
                
                NavigationLink(value: ProfileNavigationDestination.challenges) {
                    Text("挑战记录导航")
                }
                
                NavigationLink(value: ProfileNavigationDestination.subscription) {
                    Text("订阅信息导航")
                }
            }
            // 匹配枚举值,动态创建对应视图并传入ViewModel
            .navigationDestination(for: ProfileNavigationDestination.self) { destination in
                switch destination {
                case .progress:
                    ProgressView(progressViewModel: progressViewModel)
                case .challenges:
                    ChallengesView(challengesViewModel: challengesViewModel)
                case .subscription:
                    SubscriptionView(subscriptionViewModel: subscriptionViewModel)
                }
            }
        }
    }
}

3. 核心说明

  • 枚举仅用于标记导航目标,无需存储ViewModel实例,ViewModel通过@StateObject在ProfileView中实例化,生命周期与ProfileView绑定。
  • NavigationPath负责管理导航栈状态,navigationDestination根据枚举值动态创建视图,并传入正确的ViewModel引用,避免重复创建ViewModel导致状态丢失。
  • 该方案完全符合iOS16编程式导航范式,同时满足自定义视图接收ObservedObject类型参数的需求。

内容的提问来源于stack exchange,提问作者user10119132

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 19:31:02