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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 05:50:22