SwiftUI:如何借助ViewModel将选中项传递回父视图
SwiftUI父子ViewModel间选中项传递最优方案
核心原则
ViewModel应尽量独立于SwiftUI框架,禁止在ViewModel中使用@Binding,避免UI层与业务逻辑层耦合,优先用Combine的Publisher/Subscriber实现跨ViewModel的数据流同步。
针对你的疑问的具体解决方案
1. 如何将selectedItem传递给子ViewModel并用于排序?
通过子ViewModel初始化时接收父ViewModel的selectedItem Publisher,子ViewModel订阅该Publisher触发排序逻辑;子视图通过Binding获取selectedItem做UI高亮,职责分离不会导致状态混乱。
2. 是否不应将Binding传入ViewModel?
是的,@Binding是SwiftUI专属类型,会让ViewModel依赖UI框架,无法单独测试或复用。用Combine的Publisher可以实现相同的数据流同步,同时保持ViewModel的纯净性。
3. SwiftUI/Combine方案实现
通过订阅父ViewModel的@Published属性同步数据,用闭包(或Combine Subject)传递子视图的选中事件,无需回调嵌套。
完整代码示例
首先定义Item模型:
struct Item: Identifiable, Equatable { let id: UUID let name: String // 补充你的其他属性 }
父ViewModel与父视图
class ParentViewModel: ObservableObject { @Published var selectedItem: Item? func saveSelectedItem() { guard let selectedItem = selectedItem else { return } // 补充你的保存逻辑 print("保存选中项: \(selectedItem.name)") } } struct ParentView: View { @StateObject private var viewModel = ParentViewModel() var body: some View { VStack(spacing: 16) { Text("当前选中: \(viewModel.selectedItem?.name ?? "无")") .font(.title2) Button("保存选中项") { viewModel.saveSelectedItem() } .buttonStyle(.borderedProminent) // 传递父的选中状态与选中回调给子视图 ChildView( parentSelectedItem: $viewModel.selectedItem, onItemSelected: { item in viewModel.selectedItem = item } ) } .padding() } }
子ViewModel与子视图
class ChildViewModel: ObservableObject { @Published var items: [Item] = [] private var cancellables = Set<AnyCancellable>() private let parentSelectedItem: AnyPublisher<Item?, Never> private let onItemSelected: (Item) -> Void // 初始化时接收父的选中状态Publisher与选中回调 init( parentSelectedItem: AnyPublisher<Item?, Never>, onItemSelected: @escaping (Item) -> Void ) { self.parentSelectedItem = parentSelectedItem self.onItemSelected = onItemSelected // 订阅父的选中状态变化,自动触发排序 parentSelectedItem .sink { [weak self] selectedItem in self?.sortItems(around: selectedItem) } .store(in: &cancellables) // 模拟加载数据 loadItems() } func loadItems() { // 替换为你的数据获取逻辑 items = [ Item(id: UUID(), name: "Item 1"), Item(id: UUID(), name: "Item 2"), Item(id: UUID(), name: "Item 3"), Item(id: UUID(), name: "Item 4") ] // 初始排序 sortItems(around: nil) } // 根据选中项排序(示例:选中项置顶,其余按名称排序) private func sortItems(around selectedItem: Item?) { guard let selectedItem = selectedItem else { items.sort { $0.name < $1.name } return } items.sort { item1, item2 in if item1 == selectedItem { return true } if item2 == selectedItem { return false } return item1.name < item2.name } } // 子视图点击项时调用,通知父视图更新选中状态 func selectItem(_ item: Item) { onItemSelected(item) } } struct ChildView: View { @Binding private var parentSelectedItem: Item? private let onItemSelected: (Item) -> Void @StateObject private var viewModel: ChildViewModel init( parentSelectedItem: Binding<Item?>, onItemSelected: @escaping (Item) -> Void ) { self._parentSelectedItem = parentSelectedItem self.onItemSelected = onItemSelected // 初始化子ViewModel,传入父的选中状态Publisher self._viewModel = StateObject(wrappedValue: ChildViewModel( parentSelectedItem: parentSelectedItem.publisher.eraseToAnyPublisher(), onItemSelected: onItemSelected )) } var body: some View { List(viewModel.items) { item in Text(item.name) .foregroundColor(parentSelectedItem == item ? .blue : .primary) .font(parentSelectedItem == item ? .headline : .body) .onTapGesture { viewModel.selectItem(item) } } .listStyle(.plain) } }
内容的提问来源于stack exchange,提问作者Roval
相关产品推荐
相关产品推荐

