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
相关产品推荐
相关产品推荐

