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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 14:35:54