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

SwiftUI 多层push视图修改列表绑定属性时Navigation自动回退问题

问题现象

在iOS 15、Xcode 13.3 beta环境下,当在2层及以上push的视图中修改来自数组的binding属性时,属性修改完成后导航会立即自动回退。

购物列表列表编辑列表分区编辑
ShoppingListsViewShoppingListEditViewShoppingListEditsectionView

修改列表标题(仅深入1层视图)时功能正常,导航栈保持不变,返回上层后变更可正常同步;但修改分区标题(深入2层视图)时,只要对属性做一次修改就会触发导航立即回退。

复现动图

Example


相关代码

模型定义

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("编辑列表")    
    }
}

如果需要保留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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 07:36:04