iOS16中带ObservedObject的自定义SwiftUI视图适配NavigationLink
问题描述
我发现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

