SwiftUI如何通过UIWindow展示位于所有视图上层的类Toast自定义视图
SwiftUI 实现全局顶层Toast的解决方案
你当前的代码存在两个核心问题:未给UIHostingController的视图设置布局约束导致位置异常,以及未关闭hosting视图的交互拦截会影响下层控件使用。按照以下步骤修改即可满足需求:
核心修改逻辑
- 关闭hosting视图的用户交互,让所有触摸事件透传到下层视图,保证应用正常交互
- 给hosting视图设置透明背景和自动布局约束,固定Toast的展示位置
- 静态持有当前展示的
UIHostingController实例,避免实例被提前释放导致Toast消失 - 补充自动消失和重复弹出处理逻辑,避免内存泄漏和多Toast重叠
完整实现代码
public struct ToastView: View { // 可自定义参数 let message: String let duration: TimeInterval = 2.0 // 持有当前显示的Toast实例,避免被提前释放 private static var currentHosting: UIHostingController<ToastView>? static func displayToastAboveAll(message: String) { // 兼容多场景的keyWindow获取逻辑,适配iOS13+多窗口特性 guard let window = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .compactMap({$0 as? UIWindowScene}) .first?.windows .filter({$0.isKeyWindow}).first else { return } // 重复弹出时先移除已存在的Toast if let currentHosting = currentHosting { currentHosting.view.removeFromSuperview() self.currentHosting = nil } let toastView = ToastView(message: message) let hostingController = UIHostingController(rootView: toastView) // 持有当前实例 currentHosting = hostingController // 核心配置 hostingController.view.backgroundColor = .clear // 清除默认白色背景 hostingController.view.isUserInteractionEnabled = false // 禁用交互拦截,透传事件到下层 hostingController.view.translatesAutoresizingMaskIntoConstraints = false window.addSubview(hostingController.view) // 布局约束,可根据需求调整位置 NSLayoutConstraint.activate([ hostingController.view.centerXAnchor.constraint(equalTo: window.centerXAnchor), // 底部距离安全区20pt,如需顶部展示可替换为topAnchor hostingController.view.bottomAnchor.constraint(equalTo: window.safeAreaLayoutGuide.bottomAnchor, constant: -20), // 限制最大宽度,避免文字过长撑满屏幕 hostingController.view.widthAnchor.constraint(lessThanOrEqualTo: window.widthAnchor, constant: -40) ]) // 添加入场、退场动画 hostingController.view.alpha = 0 UIView.animate(withDuration: 0.3) { hostingController.view.alpha = 1 } completion: { _ in UIView.animate(withDuration: 0.3, delay: toastView.duration) { hostingController.view.alpha = 0 } completion: { _ in hostingController.view.removeFromSuperview() // 释放实例,避免内存泄漏 currentHosting = nil } } } public var body: some View { // 可自定义Toast样式 Text(message) .padding(.horizontal, 16) .padding(.vertical, 12) .background(Color.black.opacity(0.7)) .foregroundColor(.white) .cornerRadius(8) } }
使用方式
直接调用静态方法即可全局弹出:
ToastView.displayToastAboveAll(message: "这是一个Toast提示")
内容的提问来源于stack exchange,提问作者Froehly Fabrice
相关产品推荐
相关产品推荐

