SwiftUI(MVVM)中从模态添加页刷新父列表页的实现
问题:添加咖啡订单后如何通知列表页面刷新?
我拥有CoffeeOrderListScreen和AddCoffeeOrderScreen两个页面:CoffeeOrderListScreen用于展示所有咖啡订单,同时可打开模态窗口显示AddCoffeeOrderScreen。现需解决的问题是:通过API添加新订单后,如何让AddCoffeeOrderScreen通知CoffeeOrderListScreen刷新订单列表?
现有代码
CoffeeOrderListScreen
struct CoffeeOrderListScreen: View { @StateObject var vm: CoffeeOrderListViewModel @State private var isPresented: Bool = false var body: some View { List(vm.orders) { order in Text(order.name) }.task { do { try await vm.loadOrders() } catch { print(error.localizedDescription) } }.navigationTitle("Orders") .toolbar(content: { ToolbarItem(placement: .topBarTrailing) { Button("Add Order") { isPresented = true // 修正原代码笔误,改为true触发模态窗 } } }) .sheet(isPresented: $isPresented, content: { AddCoffeeOrderScreen(vm: AddCoffeeOrderViewModel(httpClient: HTTPClient.shared)) }) } }
AddCoffeeOrderViewModel
@MainActor class AddCoffeeOrderViewModel: ObservableObject { private var httpClient: HTTPClient @Published var name: String = "" @Published var coffeeName: String = "" @Published var total: Double = 0.0 @Published var size: CoffeeSize = .medium init(httpClient: HTTPClient) { self.httpClient = httpClient } func placeOrder() async throws { let coffeeOrder = CoffeeOrder(name: name, coffeeName: coffeeName, total: total, size: size) let coffeeOrderData = try JSONEncoder().encode(coffeeOrder) let resource = Resource(url: APIs.addOrder.url, method: .post(coffeeOrderData), modelType: CoffeeOrder.self) let savedCoffeeOrder = try await httpClient.load(resource) // 这里需要通知CoffeeOrderListScreen刷新列表 } }
解决方案
方案1:闭包回调(简单直接)
在AddCoffeeOrderViewModel中添加回调闭包,订单成功提交后触发该闭包,通知列表页面刷新。
修改AddCoffeeOrderViewModel
@MainActor class AddCoffeeOrderViewModel: ObservableObject { private var httpClient: HTTPClient @Published var name: String = "" @Published var coffeeName: String = "" @Published var total: Double = 0.0 @Published var size: CoffeeSize = .medium // 添加回调闭包,用于通知订单已添加 var onOrderAdded: (() -> Void)? init(httpClient: HTTPClient) { self.httpClient = httpClient } func placeOrder() async throws { let coffeeOrder = CoffeeOrder(name: name, coffeeName: coffeeName, total: total, size: size) let coffeeOrderData = try JSONEncoder().encode(coffeeOrder) let resource = Resource(url: APIs.addOrder.url, method: .post(coffeeOrderData), modelType: CoffeeOrder.self) let savedCoffeeOrder = try await httpClient.load(resource) // 触发回调 onOrderAdded?() } }
修改CoffeeOrderListScreen的Sheet配置
在创建AddCoffeeOrderViewModel实例时,绑定回调闭包,调用列表ViewModel的刷新方法:
.sheet(isPresented: $isPresented) { let addViewModel = AddCoffeeOrderViewModel(httpClient: HTTPClient.shared) // 绑定回调,刷新订单列表 addViewModel.onOrderAdded = { [weak listVM = vm] in Task { @MainActor in do { try await listVM?.loadOrders() } catch { print(error.localizedDescription) } } } AddCoffeeOrderScreen(vm: addViewModel) }
方案2:共享状态管理器(解耦推荐)
创建一个全局共享的订单状态管理器,统一处理订单的加载和提交,两个ViewModel都依赖这个管理器,订单更新时自动同步到列表。
创建CoffeeOrderManager
@MainActor class CoffeeOrderManager: ObservableObject { static let shared = CoffeeOrderManager() private let httpClient = HTTPClient.shared // 共享的订单列表,自动通知视图刷新 @Published var orders: [CoffeeOrder] = [] private init() {} // 加载订单逻辑 func loadOrders() async throws { let resource = Resource(url: APIs.orders.url, method: .get, modelType: [CoffeeOrder].self) orders = try await httpClient.load(resource) } // 提交订单逻辑 func placeOrder(_ order: CoffeeOrder) async throws { let orderData = try JSONEncoder().encode(order) let resource = Resource(url: APIs.addOrder.url, method: .post(orderData), modelType: CoffeeOrder.self) let savedOrder = try await httpClient.load(resource) // 可以直接添加到列表,或者重新加载确保与服务器一致 orders.append(savedOrder) // 或者调用loadOrders()刷新完整列表 // try await loadOrders() } }
修改CoffeeOrderListViewModel
@MainActor class CoffeeOrderListViewModel: ObservableObject { private let orderManager = CoffeeOrderManager.shared @Published var orders: [CoffeeOrder] = [] init() { // 监听共享管理器的订单变化,自动同步 orderManager.$orders.assign(to: &$orders) } func loadOrders() async throws { try await orderManager.loadOrders() } }
修改AddCoffeeOrderViewModel
@MainActor class AddCoffeeOrderViewModel: ObservableObject { private let orderManager = CoffeeOrderManager.shared @Published var name: String = "" @Published var coffeeName: String = "" @Published var total: Double = 0.0 @Published var size: CoffeeSize = .medium func placeOrder() async throws { let coffeeOrder = CoffeeOrder(name: name, coffeeName: coffeeName, total: total, size: size) try await orderManager.placeOrder(coffeeOrder) // 无需额外通知,共享管理器的orders更新后,列表会自动刷新 } }
方案3:环境对象(SwiftUI原生方式)
基于方案2的共享管理器,使用SwiftUI的EnvironmentObject在视图间传递状态,进一步简化依赖传递。
在根视图注入环境对象
// 例如在App入口或导航栈根视图 CoffeeOrderListScreen(vm: CoffeeOrderListViewModel()) .environmentObject(CoffeeOrderManager.shared)
修改AddCoffeeOrderViewModel使用环境对象
@MainActor class AddCoffeeOrderViewModel: ObservableObject { @EnvironmentObject private var orderManager: CoffeeOrderManager @Published var name: String = "" @Published var coffeeName: String = "" @Published var total: Double = 0.0 @Published var size: CoffeeSize = .medium func placeOrder() async throws { let coffeeOrder = CoffeeOrder(name: name, coffeeName: coffeeName, total: total, size: size) try await orderManager.placeOrder(coffeeOrder) } }
方案对比
- 闭包回调:实现简单,适合小型项目或两个页面直接关联的场景,但页面间耦合度较高。
- 共享状态管理器:解耦页面依赖,统一管理订单状态,适合中大型项目,便于扩展和维护。
- 环境对象:SwiftUI原生状态传递方式,无需手动传递管理器实例,适合多视图共享同一状态的场景。
内容的提问来源于stack exchange,提问作者john doe
相关产品推荐
相关产品推荐

