SwiftUI NavigationLink进入子视图后自动返回父视图问题排查
SwiftUI三层导航自动返回问题分析及修复
问题原因
错误的绑定方式触发视图状态重置
在ContentView中,你通过$vm.items[vm.items.firstIndex(of: item)!]给NavigationLink的目标视图传递绑定。这种方式存在致命问题:- 每当ViewModel的
@Published属性更新时(比如进入第三层视图调用fetchParentItems更新parentItems),ContentView会重新计算body,此时firstIndex(of: item)会重新执行。由于MyItem是类,即使id相同,对象引用可能存在差异(allItems的实例创建逻辑会生成新对象),导致索引查找结果不稳定,触发NavigationLink的绑定目标异常变化,SwiftUI会自动重置导航状态,返回第二层视图。 - 直接绑定数组索引位置的元素,当数组结构变化时,索引对应的元素可能改变,进一步加剧状态异常。
- 每当ViewModel的
数据实例重复创建引发不必要的视图更新
ViewModel的allItems虽然是静态属性,但如果后续操作重新生成实例,或者fetchParentItems返回的数组元素是新实例(即使id相同),会让SwiftUI的ForEach认为数据发生了变化,引发不必要的视图更新,间接影响导航状态的稳定性。
修改方案
方案1:简化视图参数传递,移除不必要的Binding
如果不需要在DetailView中修改MyItem的属性,完全不需要用@Binding传递,直接传递实例即可:
修改ContentView的NavigationLink
NavigationLink(destination: DetailView(myItem: item)) { Text(item.title) }
修改DetailView的属性定义
struct DetailView: View { @EnvironmentObject var vm: ViewModel let myItem: MyItem // 改为常量实例,无需绑定 var body: some View { VStack { Text(myItem.title) NavigationLink(destination: ParentItemList()) { Text("Parent Item") } } } }
方案2:优化ViewModel的数据管理(可选)
确保allItems中的实例是固定的,避免重复创建相同id的不同实例,这样firstIndex(of:)的结果会更稳定(如果后续需要使用绑定的话):
// ViewModel中的allItems保持静态初始化一次即可(当前代码已经是这样,无需修改,注意后续不要重新生成实例) private static let allItems: [MyItem] = { var itemList: [MyItem] = [] for index in 0..<9 { let listItem = MyItem(id: UUID().uuidString, title: "Title\(index)", parentId: nil) itemList.append(listItem) } return itemList }()
完整修复代码
import PlaygroundSupport import SwiftUI class MyItem: Identifiable, Equatable { static func == (lhs: MyItem, rhs: MyItem) -> Bool { lhs.id == rhs.id } var id: String var title: String var parentId: String? init(id: String, title: String, parentId: String?) { self.id = id self.title = title self.parentId = parentId } } class ViewModel: ObservableObject { @Published var items: [MyItem] = [] @Published var parentItems: [MyItem] = [] init() { fetchItmes() } private static let allItems: [MyItem] = { var itemList: [MyItem] = [] for index in 0..<9 { let listItem = MyItem(id: UUID().uuidString, title: "Title\(index)", parentId: nil) itemList.append(listItem) } return itemList }() func fetchItmes() { items = ViewModel.allItems } func fetchParentItems() { parentItems = Array(ViewModel.allItems.prefix(3)) } } struct ContentView: View { @StateObject var vm = ViewModel() var body: some View { NavigationView { List { ForEach(vm.items) { item in NavigationLink(destination: DetailView(myItem: item)) { Text(item.title) } } } .navigationTitle("All Items") } .frame(width: 375, height: 667) .environmentObject(vm) } } struct DetailView: View { @EnvironmentObject var vm: ViewModel let myItem: MyItem var body: some View { VStack { Text(myItem.title) NavigationLink(destination: ParentItemList()) { Text("Parent Item") } } } } struct ParentItemList: View { @EnvironmentObject var vm: ViewModel var body: some View { List { ForEach(vm.parentItems) { item in Text(item.title) } } .onAppear { vm.fetchParentItems() } } } PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
内容的提问来源于stack exchange,提问作者Woody
相关产品推荐
相关产品推荐

