SwiftUI实现独立二级嵌套列表的技术方案咨询
SwiftUI独立二级自定义列表实现问题
我是Swift初学者,想开发一款支持自定义二级列表的SwiftUI应用:一级列表的每个条目下都要有独立的二级列表(各二级列表内容不重复)。目前已经搭好了NavigationView嵌套列表的基本结构,但没法实现二级列表的独立性,附上现有代码,求解决思路。
当前效果:
预期效果:
现有代码
struct ItemModel: Hashable { let name: String } struct ProductModel: Hashable { let productname: String } class ListViewModel: ObservableObject { @Published var items: [ItemModel] = [] } class ProductlistViewModel: ObservableObject { @Published var products: [ProductModel] = [] } struct ContentView: View { @StateObject private var vm = ListViewModel() @StateObject private var pvm = ProductlistViewModel() @State var firstPlusButtonPressed: Bool = false @State var secondPlusButtonPressed: Bool = false var body: some View { NavigationView { List { ForEach(vm.items, id: \.self) { item in NavigationLink { DetailView() .navigationTitle(item.name) .navigationBarItems( trailing: Button(action: { secondPlusButtonPressed.toggle() }, label: { NavigationLink { AddProduct() } label: { Image(systemName: "plus") } }) ) } label: { Text(item.name) } } } .navigationBarItems( trailing: Button(action: { firstPlusButtonPressed.toggle() }, label: { NavigationLink { AddItem() } label: { Image(systemName: "plus") } }) ) } .environmentObject(vm) .environmentObject(pvm) } } struct AddItem: View { @State var textFieldText: String = "" @Environment(\.presentationMode) var presentationMode @EnvironmentObject var vm: ListViewModel var body: some View { NavigationView { VStack { TextField("Add an item...", text: $textFieldText) Button(action: { vm.addItem(text: textFieldText) presentationMode.wrappedValue.dismiss() }, label: { Text("SAVE") }) } } } } struct DetailView: View { @StateObject private var pvm = ProductlistViewModel() @Environment(\.editMode) var editMode var body: some View { NavigationView { List { ForEach(pvm.products, id: \.self) { product in Text(product.productname) } } } .environmentObject(pvm) } } struct AddProduct: View { @State var textFieldText: String = "" @Environment(\.presentationMode) var presentationMode @EnvironmentObject var pvm: ProductlistViewModel var body: some View { NavigationView { VStack { TextField("Add a product", text: $textFieldText) Button(action: { pvm.addProduct(text: textFieldText) presentationMode.wrappedValue.dismiss() }, label: { Text("SAVE") }) } } } }
解决思路与修改方案
核心问题分析
当前代码的问题在于:所有一级条目共享同一个ProductlistViewModel,或者每个DetailView重新创建独立的ProductlistViewModel,导致要么所有二级列表内容同步,要么添加的产品无法和对应一级条目关联。要实现独立性,必须让每个一级Item绑定自己的二级Product列表。
具体修改步骤
- 重构数据模型:让
ItemModel包含自己的products数组,同时给每个Item添加唯一ID(避免Hashable冲突) - 统一ViewModel:用一个
MainViewModel管理所有一级Item及其对应的二级Product,替代原来的两个分离ViewModel - 视图传递对应数据:在
DetailView和AddProduct中传递当前选中的Item ID,确保操作的是对应Item的Product列表
修改后的完整代码
import SwiftUI // 给模型添加唯一ID,避免Hashable冲突 struct ItemModel: Identifiable, Hashable { let id = UUID() var name: String var products: [ProductModel] = [] // 每个Item自带独立的Product列表 } struct ProductModel: Identifiable, Hashable { let id = UUID() var productname: String } // 统一管理所有数据的ViewModel class MainViewModel: ObservableObject { @Published var items: [ItemModel] = [] // 添加一级Item func addItem(name: String) { guard !name.isEmpty else { return } items.append(ItemModel(name: name)) } // 给指定Item添加Product func addProduct(to itemId: UUID, productName: String) { guard !productName.isEmpty else { return } guard let index = items.firstIndex(where: { $0.id == itemId }) else { return } items[index].products.append(ProductModel(productname: productName)) } } struct ContentView: View { @StateObject private var vm = MainViewModel() var body: some View { NavigationStack { // 用NavigationStack替代旧的NavigationView(iOS16+) List { ForEach($vm.items) { $item in // 使用绑定传递Item NavigationLink { DetailView(item: $item) .navigationTitle(item.name) } label: { Text(item.name) } } } .navigationTitle("一级列表") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { NavigationLink { AddItemView(vm: vm) } label: { Image(systemName: "plus") } } } } } } struct AddItemView: View { @State private var textFieldText = "" @Environment(\.dismiss) var dismiss // 用dismiss替代presentationMode(iOS15+) let vm: MainViewModel var body: some View { NavigationStack { VStack(spacing: 20) { TextField("输入一级条目名称", text: $textFieldText) .textFieldStyle(.roundedBorder) .padding() Button("保存") { vm.addItem(name: textFieldText) dismiss() } .buttonStyle(.borderedProminent) } .navigationTitle("添加一级条目") } } } struct DetailView: View { @Binding var item: ItemModel // 绑定当前Item,直接操作其products var body: some View { NavigationStack { List { ForEach(item.products) { product in Text(product.productname) } } .navigationTitle(item.name) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { NavigationLink { AddProductView(itemId: item.id, vm: MainViewModel.shared) // 传递当前Item的ID } label: { Image(systemName: "plus") } } } } } } struct AddProductView: View { @State private var textFieldText = "" @Environment(\.dismiss) var dismiss let itemId: UUID let vm: MainViewModel var body: some View { NavigationStack { VStack(spacing: 20) { TextField("输入二级产品名称", text: $textFieldText) .textFieldStyle(.roundedBorder) .padding() Button("保存") { vm.addProduct(to: itemId, productName: textFieldText) dismiss() } .buttonStyle(.borderedProminent) } .navigationTitle("添加产品") } } } // 给ViewModel添加单例,方便跨视图访问(也可以用EnvironmentObject传递) extension MainViewModel { static let shared = MainViewModel() } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() .environmentObject(MainViewModel.shared) } }
关键说明
- 用
Identifiable替代手动实现Hashable,避免重复数据冲突 - 每个
ItemModel自带products数组,天然保证二级列表独立性 - 使用
NavigationStack替代旧的NavigationView(适配iOS16+,更稳定) - 用
@Binding和dismiss替代旧的presentationMode,简化视图 Dismiss 逻辑 - 单例
MainViewModel方便跨视图访问数据,也可以继续用EnvironmentObject传递
内容的提问来源于stack exchange,提问作者Christina
相关产品推荐
相关产品推荐

