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

如何在Swift/SwiftUI中监听一个ViewModel的数据变化并通知另一个?

跨ViewModel状态同步:选中房屋后自动更新房间列表

你之前用UserDefaults配合viewDidAppear的方式确实不够规范,还可能带来不必要的副作用。要实现选中房屋后自动更新房间列表的效果,核心是让两个子视图共享同一个HousePickerViewModel实例,再通过SwiftUI的响应式机制触发房间加载逻辑。

以下是两种推荐的实现方案:


方案一:View层通过onChange监听选中状态变化

这种方式简单直接,适合逻辑不复杂的场景:

修改后的完整代码

import SwiftUI

struct HomeView: View {
    // 将HousePickerViewModel提升到父视图,让两个子视图共享
    @StateObject private var housePickerVM = HousePickerViewModel()
    
    var body: some View {
        TabView {
            RoomsView(housePickerVM: housePickerVM)
                .tabItem {
                    Label("Rooms", systemImage: "pencil")
                }
            
            HousePickerView(housePickerVM: housePickerVM)
                .tabItem {
                    Label("House Picker", systemImage: "magnifyingglass")
                }
        }
    }
}

struct RoomsView: View {
    @StateObject private var roomsVM = RoomsViewModel()
    // 接收共享的HousePickerViewModel实例
    @ObservedObject var housePickerVM: HousePickerViewModel
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("Rooms:")
                Text(roomsVM.roomsBasedOnHouse)
            }
            // 监听selectedHouse的变化,触发房间加载
            .onChange(of: housePickerVM.selectedHouse) { newHouse in
                if let house = newHouse {
                    roomsVM.loadRoomsForHouse(house: house)
                }
            }
        }
    }
}

struct HousePickerView: View {
    // 接收共享的HousePickerViewModel实例
    @ObservedObject var housePickerVM: HousePickerViewModel
    
    var body: some View {
        NavigationStack {
            List(housePickerVM.houses) { house in
                HStack {
                    if house.id == housePickerVM.selectedHouse?.id {
                        Image(systemName: "checkmark")
                    }
                    Text(house.name)
                }
                .onTapGesture {
                    housePickerVM.pickHouse(house: house)
                }
            }
        }
    }
}

class RoomsViewModel: ObservableObject {
    @Published var roomsBasedOnHouse: String = ""
    
    func loadRoomsForHouse(house: House) {
        if house.name == "Home" {
            roomsBasedOnHouse = "Living Room, Bedroom, Garage"
        } else if house.name == "Vacation Home" {
            roomsBasedOnHouse = "Patio, Pool Room, Bar"
        }
    }
}

class HousePickerViewModel: ObservableObject {
    @Published var houses = [House](arrayLiteral: House(id: "abc", name: "Home"), House(id: "def", name: "Vacation Home"))
    @Published var selectedHouse: House? = nil
    
    func pickHouse(house: House) {
        selectedHouse = house
    }
}

public struct House: Identifiable {
    public var id : String
    public var name: String
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}

方案二:ViewModel层通过Combine订阅状态变化

如果想把状态监听逻辑封装在ViewModel内部,更符合MVVM的职责分离,可以用Combine框架实现订阅:

修改后的核心代码

更新RoomsViewModel

import SwiftUI
import Combine

class RoomsViewModel: ObservableObject {
    @Published var roomsBasedOnHouse: String = ""
    private var cancellables = Set<AnyCancellable>()
    
    // 订阅HousePickerViewModel的选中状态变化
    func subscribeToHouseSelection(_ housePickerVM: HousePickerViewModel) {
        housePickerVM.$selectedHouse
            .compactMap { $0 } // 过滤掉nil值
            .sink { [weak self] house in
                self?.loadRoomsForHouse(house: house)
            }
            .store(in: &cancellables)
    }
    
    func loadRoomsForHouse(house: House) {
        if house.name == "Home" {
            roomsBasedOnHouse = "Living Room, Bedroom, Garage"
        } else if house.name == "Vacation Home" {
            roomsBasedOnHouse = "Patio, Pool Room, Bar"
        }
    }
}

更新RoomsView

struct RoomsView: View {
    @StateObject private var roomsVM = RoomsViewModel()
    @ObservedObject var housePickerVM: HousePickerViewModel
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("Rooms:")
                Text(roomsVM.roomsBasedOnHouse)
            }
        }
        .onAppear {
            // 页面出现时订阅状态变化
            roomsVM.subscribeToHouseSelection(housePickerVM)
            // 初始化时如果已有选中房屋,加载对应房间
            if let house = housePickerVM.selectedHouse {
                roomsVM.loadRoomsForHouse(house: house)
            }
        }
    }
}

核心思路说明

  1. 共享ViewModel实例:把HousePickerViewModel的创建从子视图提升到父视图HomeView,再传递给两个子视图,确保选中状态在全局是唯一且同步的。
  2. 响应式触发逻辑:通过SwiftUI的onChange修饰符或Combine的订阅机制,监听selectedHouse的变化,自动调用loadRoomsForHouse更新房间列表。

内容的提问来源于stack exchange,提问作者Cameron Henige

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 19:53:21