SwiftUI 多层push视图修改列表绑定属性时Navigation自动回退问题
问题现象
在iOS 15、Xcode 13.3 beta环境下,当在2层及以上push的视图中修改来自数组的binding属性时,属性修改完成后导航会立即自动回退。
| 购物列表 | 列表编辑 | 列表分区编辑 |
|---|---|---|
![]() | ![]() | ![]() |
修改列表标题(仅深入1层视图)时功能正常,导航栈保持不变,返回上层后变更可正常同步;但修改分区标题(深入2层视图)时,只要对属性做一次修改就会触发导航立即回退。
复现动图

相关代码
模型定义
struct ShoppingList { let id: String = UUID().uuidString var title: String var sections: [ShoppingListSection] } struct ShoppingListSection { let id: String = UUID().uuidString var title: String }
视图模型
final class ShoppingListsViewModel: ObservableObject { @Published var shoppingLists: [ShoppingList] = [ .init( title: "Shopping List 01", sections: [ .init(title: "Fresh food") ] ) ] }
入口视图
struct ContentView: View { var body: some View { NavigationView { ShoppingListsView() } } }
购物列表页
struct ShoppingListsView: View { @StateObject private var viewModel = ShoppingListsViewModel() var body: some View { List($viewModel.shoppingLists, id: \.id) { $shoppingList in NavigationLink(destination: ShoppingListEditView(shoppingList: $shoppingList)) { Text(shoppingList.title) } } .navigationBarTitle("购物列表") } }
列表编辑页
struct ShoppingListEditView: View { @Binding var shoppingList: ShoppingList var body: some View { Form { Section(header: Text("标题")) { TextField("请输入标题", text: $shoppingList.title) } Section(header: Text("分区")) { List($shoppingList.sections, id: \.id) { $section in NavigationLink(destination: ShoppingListSectionEditView(section: $section)) { Text(section.title) } } } } .navigationBarTitle("编辑列表") } }
分区编辑页
struct ShoppingListSectionEditView: View { @Binding var section: ShoppingListSection var body: some View { Form { Section(header: Text("标题")) { TextField("请输入标题", text: $section.title) } } .navigationBarTitle("编辑分区") } }
问题根源
该问题属于iOS 15及更早版本中SwiftUI的已知行为:
- 嵌套在
List/ForEach中的默认NavigationLink(无显式tag/selection绑定)的激活状态和父列表的刷新强绑定 - 当你在分区编辑页修改
section.title时,变更会向上回溯更新shoppingList.sections数组,触发ShoppingListEditView的body重绘,其内部的sections列表刷新,对应的NavigationLink激活状态被重置为false,系统就会自动pop回上一级页面 - 第一层修改列表标题不会触发回退,是因为第一层的List在根视图
ShoppingListsView中,修改shoppingList的title只会触发ShoppingListEditView刷新,不会触发根视图的List刷新,所以第一层的NavigationLink状态得以保留
解决方案
提供两种适配iOS 15+的可行方案:
方案1:将嵌套的List替换为ForEach(改动量最小,优先推荐)
如果不需要列表的滑动删除、排序等特性,直接用ForEach嵌套在Section里即可,避免List刷新导致的NavigationLink状态重置:
struct ShoppingListEditView: View { @Binding var shoppingList: ShoppingList var body: some View { Form { Section(header: Text("标题")) { TextField("请输入标题", text: $shoppingList.title) } Section(header: Text("分区")) { // 把List替换为ForEach ForEach($shoppingList.sections, id: \.id) { $section in NavigationLink(destination: ShoppingListSectionEditView(section: $section)) { Text(section.title) } } } } .navigationBarTitle("编辑列表") } }
方案2:使用基于tag/selection的NavigationLink
如果需要保留List的全部特性,可以给NavigationLink绑定显式的选中状态,避免列表刷新时激活状态丢失:
struct ShoppingListEditView: View { @Binding var shoppingList: ShoppingList // 新增选中分区ID的状态 @State private var selectedSectionId: String? var body: some View { Form { Section(header: Text("标题")) { TextField("请输入标题", text: $shoppingList.title) } Section(header: Text("分区")) { List($shoppingList.sections, id: \.id) { $section in Button { selectedSectionId = section.id } label: { Text(section.title) .frame(maxWidth: .infinity, alignment: .leading) } } // 把NavigationLink放在List的背景层,和选中状态绑定 .background( NavigationLink(isActive: .constant(selectedSectionId != nil), destination: { if let id = selectedSectionId, let index = shoppingList.sections.firstIndex(where: {$0.id == id}) { ShoppingListSectionEditView(section: $shoppingList.sections[index]) } }, label: { EmptyView() }) ) } } .navigationBarTitle("编辑列表") } }
内容的提问来源于stack exchange,提问作者lacking-cypher
相关产品推荐
相关产品推荐




