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

SwiftUI 如何从ObservableObject类函数触发错误弹窗

SwiftUI下ViewModel触发视图弹窗的实现方案

核心逻辑说明

遵循ObservableObject协议的ViewModel不能直接操作视图,需要通过@Published修饰的发布状态向视图传递弹窗触发信号,视图监听该状态的变化自动渲染弹窗,符合SwiftUI单向数据流的设计逻辑。

分步实现

1. 在ViewModel中定义可监听的弹窗状态

在delivViewModel类中新增@Published修饰的弹窗属性,同时为了满足系统.alert修饰符的绑定要求,定义遵守Identifiable协议的弹窗内容模型:

// 定义弹窗内容模型
struct AlertContent: Identifiable {
    let id = UUID()
    let title: String
    let message: String
}

class delivViewModel: ObservableObject {
    // 原有属性(数据库实例、配送数据缓存等)
    // 可选类型的弹窗属性:nil代表默认不展示弹窗
    @Published var alertContent: AlertContent?

    // 原有cancelDeliv方法后续修改
}

2. 修改cancelDeliv方法,在条件命中时更新弹窗状态

注意异步回调中要把UI状态的更新切回主线程,同时替换原有强制解包的逻辑避免崩溃:

func cancelDeliv(delivID: String) {
    let delivRef = db.collection("av_deliveries").document(delivID)
    let isDemain = Date().dayAfter.toString()
    delivRef.getDocument { [weak self] (document, error) in
        // 所有UI状态更新统一切回主线程,避免后台线程更新@Published的运行时警告
        DispatchQueue.main.async {
            // 处理请求异常场景
            if let error = error {
                self?.alertContent = AlertContent(title: "加载失败", message: error.localizedDescription)
                return
            }
            guard let document = document, document.exists else {
                self?.alertContent = AlertContent(title: "操作失败", message: "未查询到对应配送单")
                return
            }
            // 安全解包配送时间字段,替换原有强制解包
            guard let delivTime = document.get("when") as? String else {
                self?.alertContent = AlertContent(title: "数据异常", message: "配送时间格式无效")
                return
            }

            if isDemain == delivTime {
                // 满足弹窗判定条件:给alertContent赋值,视图会自动监听到变化弹出弹窗
                self?.alertContent = AlertContent(title: "无法取消", message: "预约为明天的配送单暂不支持取消")
            } else {
                // 不满足弹窗条件,继续执行原有更新逻辑
                delivRef.updateData([
                    "taken": false,
                    "taken_by": ""
                ]) { err in
                    DispatchQueue.main.async {
                        if let err = err {
                            self?.alertContent = AlertContent(title: "取消失败", message: err.localizedDescription)
                        } else {
                            print("Document successfully updated")
                        }
                    }
                }
            }
        }
    }
}

3. 在对应SwiftUI视图中绑定弹窗状态

在触发cancelDeliv的视图中,绑定ViewModel的alertContent属性,通过系统.alert修饰符实现弹窗渲染:

struct DeliveryDetailView: View {
    // 绑定ViewModel:如果是父视图传入的ViewModel请替换为@ObservedObject
    @StateObject private var delivViewModel = DelivViewModel()
    let delivID: String

    var body: some View {
        VStack(spacing: 20) {
            // 原有视图内容
            Button("取消该配送") {
                delivViewModel.cancelDeliv(delivID: delivID)
            }
        }
        // 绑定弹窗:当alertContent非nil时自动弹出,点击按钮后自动关闭
        .alert(item: $delivViewModel.alertContent) { content in
            // 自定义弹窗按钮,可根据需求调整
            Button("知道了", role: .cancel) {}
        }
    }
}

注意事项

  • 异步回调中必须把@Published属性的更新切回主线程,避免出现UI更新延迟、运行时警告等问题
  • 不要在ViewModel中直接引入UIKit或SwiftUI的视图组件,仅通过状态传递实现视图响应,保持ViewModel的可测试性
  • 原有代码中对Firestore字段的强制解包as! String极易因为字段缺失、类型不匹配引发崩溃,替换为安全解包逻辑后稳定性更强

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.28 04:36:10