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

SwiftUI通用Toast视图修饰符不响应ViewModel状态变更问题

问题分析与解决方案

问题描述

我用Toast库做错误/成功提示,为了避免重复写代码,封装了通用视图修饰符ErrorToast,但遇到了问题:从页面A跳转到页面B执行操作触发错误时,Toast完全没反应;返回A再重新进入B页面,Toast才会显示。这说明error变量确实被正确赋值了,但视图没响应变量的更新。

相关代码

1. ErrorToast 视图修饰符

struct ErrorToast: ViewModifier {
    let actionCompletedViewModel: ActionCompletedViewModel
    func body(content: Content) -> some View {
        content
            .toast(isPresenting: .constant(actionCompletedViewModel.error != nil), duration: 2, tapToDismiss: true,
                   alert: {
                let errorMessage = actionCompletedViewModel.error?.errorDescription
                let errorTitle = actionCompletedViewModel.error?.title
                return AlertToast(displayMode: .alert, type: .error(.red), title: errorTitle, subTitle: errorMessage)
            }, onTap: {
                actionCompletedViewModel.handleErrorAndClear()
            })
    }
}

2. 父类 ViewModel

class ActionCompletedViewModel: ObservableObject {
    @Published var error: CustomError?
    @Published var success: CustomSuccess?
    func assignError(error: CustomError) {
        DispatchQueue.main.async {[weak self] in
            self?.error = error
        }
    }
    // 其他代码省略
}

3. 业务视图与子类 ViewModel

struct AView: View {
    @ObservedObject var aViewModel = AViewModel.shared
    var body: some View {
        VStack {
            Button {
                aViewModel.doAction()
            } label: {
                Text("执行操作")
            }
        }
        .errorToast(aViewModel: aViewModel)
    }
}

class AViewModel: ActionCompletedViewModel, ObservableObject {
    func doAction() {
        someOtherAction() { result in
            DispatchQueue.main.async {
                switch result {
                case .success(let success): self.assignSuccess(success: success)
                case .failure(let error): self.assignError(error: error)
                }
            }
        }
    }
    // 异步操作模拟代码省略
}

问题根源

  1. 静态绑定导致不响应更新:isPresenting用了.constant(actionCompletedViewModel.error != nil),这是个一次性计算的静态值,只有视图第一次渲染时会判断,后续error变化完全不会触发绑定更新,自然弹不出Toast。
  2. ObservableObject监听方式错误:之前尝试给ErrorToast里的actionCompletedViewModel加@ObservedObject但没用,因为原来的属性是let声明的,@ObservedObject只能修饰var类型的属性,才能监听ViewModel的@Published变量变化。

修复方案

修改ErrorToast的实现,改成能动态监听error变化的形式:

struct ErrorToast: ViewModifier {
    @ObservedObject var actionCompletedViewModel: ActionCompletedViewModel
    
    func body(content: Content) -> some View {
        content
            .toast(isPresenting: $actionCompletedViewModel.error.isNotNilBinding, duration: 2, tapToDismiss: true,
                   alert: {
                AlertToast(
                    displayMode: .alert,
                    type: .error(.red),
                    title: actionCompletedViewModel.error?.title,
                    subTitle: actionCompletedViewModel.error?.errorDescription
                )
            }, onTap: {
                actionCompletedViewModel.handleErrorAndClear()
            })
    }
}

// 给Optional扩展双向绑定的判断属性
extension Optional {
    var isNotNil: Bool { self != nil }
    
    var isNotNilBinding: Binding<Bool> {
        Binding(
            get: { self.isNotNil },
            set: { newValue in
                if !newValue {
                    self = nil
                }
            }
        )
    }
}

// 给View加便捷调用方法
extension View {
    func errorToast(viewModel: ActionCompletedViewModel) -> some View {
        self.modifier(ErrorToast(actionCompletedViewModel: viewModel))
    }
}

关键修改点

  • 把ErrorToast里的let actionCompletedViewModel改成@ObservedObject var actionCompletedViewModel,这样就能监听到ViewModel中@Published变量的变化。
  • 用自定义的isNotNilBinding替代.constant,实现双向响应:error赋值时自动弹出Toast,Toast关闭时自动清空error,避免重复触发。
  • 补充handleErrorAndClear的实现(如果之前没写):
class ActionCompletedViewModel: ObservableObject {
    // 原有代码...
    func handleErrorAndClear() {
        DispatchQueue.main.async {[weak self] in
            self?.error = nil
        }
    }
}

这样修改后,当error被赋值时,ErrorToast会立刻感知到变化并弹出Toast;关闭Toast时也会自动清空error,确保下次错误能正常触发提示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 15:33:09