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

SwiftUI中如何为自定义Toast组件注入自定义DismissAction到环境中?

SwiftUI中如何为自定义Toast组件注入自定义DismissAction到环境中?

你提到的这个需求特别常见——想让自定义弹窗类组件和原生的sheet、fullScreenCover一样,不用到处传绑定值,直接通过环境里的dismiss方法关闭。其实核心思路就是自定义一个环境键,把关闭Toast的逻辑包装后注入到环境中,下面一步步来实现:

第一步:自定义Toast专属的DismissAction环境键

首先我们要创建自己的环境键,用来存储关闭Toast的操作,还要让它的用法和系统的DismissAction保持一致:

// 封装关闭Toast的操作类型
struct ToastDismissAction {
    private let action: () -> Void
    
    init(action: @escaping () -> Void) {
        self.action = action
    }
    
    // 实现callAsFunction,让我们可以像调用系统dismiss()一样使用
    func callAsFunction() {
        action()
    }
}

// 自定义环境键,用来在环境中存我们的DismissAction
private struct ToastDismissEnvironmentKey: EnvironmentKey {
    // 设置默认值,防止没注入时报错,默认什么都不做
    static let defaultValue: ToastDismissAction = ToastDismissAction(action: {})
}

// 扩展EnvironmentValues,方便通过@Environment访问
extension EnvironmentValues {
    var toastDismiss: ToastDismissAction {
        get { self[ToastDismissEnvironmentKey.self] }
        set { self[ToastDismissEnvironmentKey.self] = newValue }
    }
}

第二步:修改自定义Toast组件,注入环境值

接下来修改你的MyToastView,在内容视图外层包裹environment修饰符,把关闭isPresented的逻辑传递进去:

struct MyToastView<Content: View>: View {
    @ViewBuilder let content: Content
    @Binding var isPresented: Bool
    
    var body: some View {
        if isPresented {
            VStack(alignment: .leading) {
                content
            }
            .padding()
            .background {
                Rectangle()
                    .fill(.blue)
            }
            .transition(.opacity)
            // 把关闭Toast的逻辑注入到环境中
            .environment(\.toastDismiss, ToastDismissAction(action: {
                isPresented = false
            }))
        }
    }
}

这样一来,所有在content里的子视图,都能通过环境拿到这个关闭操作了。

第三步:保持原有View扩展(无需改动)

你的myToast扩展可以完全保留,因为环境注入已经在MyToastView里处理好了:

extension View {
    func myToast<T: View>(isPresented: Binding<Bool>, @ViewBuilder content: () -> T) -> some View {
        overlay {
            MyToastView(content: content(), isPresented: isPresented)
        }
    }
}

第四步:在Toast内容视图中用环境关闭

现在你就可以像用系统组件那样,直接通过环境变量关闭Toast,不用再传递isPresented绑定了:

struct ToastContents: View {
    // 使用自定义的环境键
    @Environment(\.toastDismiss) var dismiss
    
    var body: some View {
        VStack {
            Text("这是自定义Toast")
            Button("关闭") {
                // 和系统dismiss()用法完全一样
                dismiss()
            }
            .foregroundColor(.white)
        }
    }
}

最终调用方式更简洁

现在调用的时候,不用再把isPresented传递给Toast内容视图了:

struct ContentView: View {
    @State private var isToastPresented = false
    
    var body: some View {
        Button("显示Toast") {
            isToastPresented = true
        }
        .myToast(isPresented: $isToastPresented) {
            ToastContents()
        }
    }
}

补充:为什么不能直接用系统的DismissAction?

你之前尝试直接注入系统的DismissAction失败,是因为系统的DismissAction是内部实现的,没有公开的初始化方法,而且它对应的环境键是只读的(不是WritableKeyPath),所以我们没法直接替换它。自定义环境键是最稳妥的方案,还能做到和系统用法一致的体验。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 13:05:28