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

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列表。

具体修改步骤

  1. 重构数据模型:让ItemModel包含自己的products数组,同时给每个Item添加唯一ID(避免Hashable冲突)
  2. 统一ViewModel:用一个MainViewModel管理所有一级Item及其对应的二级Product,替代原来的两个分离ViewModel
  3. 视图传递对应数据:在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 04:05:24