如何在SwiftUI中让Toast提示显示在.sheet之上?
如何在SwiftUI中让Toast提示显示在.sheet之上?
嘿,这个问题我之前也踩过坑!SwiftUI里的.sheet其实是在一个独立的展示层级里,相当于新开了一个小窗口,所以你在原来的视图栈里加的Toast自然会被它压在下面,zIndex根本没用——因为它们不在同一个层级嘛!
下面给你两种靠谱的解决方案,按需选就行:
方法一:把Toast嵌入Sheet内部
如果你的Toast是和Sheet里的操作绑定的(比如Sheet内点击按钮触发提示),这是最简单直接的办法——把Toast的逻辑直接写到Sheet的视图里,这样两者就在同一个层级了,自然能显示在上面。
示例代码:
struct ContentView: View { @State private var isSheetPresented = false @State private var showToast = false var body: some View { Button("打开Sheet") { isSheetPresented = true } .sheet(isPresented: $isSheetPresented) { SheetView(showToast: $showToast) .toast(isShowing: $showToast, message: "操作成功!") } } } struct SheetView: View { @Binding var showToast: Bool var body: some View { Button("触发Toast") { showToast = true // 2秒后自动隐藏Toast DispatchQueue.main.asyncAfter(deadline: .now() + 2) { showToast = false } } } } // 给View扩展一个Toast方法 extension View { func toast(isShowing: Binding<Bool>, message: String) -> some View { self.overlay( Group { if isShowing.wrappedValue { VStack { Spacer() Text(message) .padding() .background(Color.black.opacity(0.7)) .foregroundColor(.white) .cornerRadius(8) .padding(.bottom, 40) } .transition(.opacity) .animation(.easeInOut, value: isShowing.wrappedValue) } } ) } }
方法二:全局Toast(跨场景通用)
如果你的Toast是全局通用的(比如网络请求失败提示,不管当前有没有Sheet都要显示在最顶层),那得借助UIKit的窗口层级来实现——因为SwiftUI本身的视图栈搞不定跨Sheet的全局覆盖。
核心思路是把Toast挂载到当前最顶层的keyWindow上,这样不管Sheet还是其他视图,都会被它盖在下面。
示例代码:
// 全局Toast管理器,单例模式方便调用 class ToastManager: ObservableObject { static let shared = ToastManager() private var toastVC: UIViewController? func showToast(message: String, duration: TimeInterval = 2) { // 先移除之前的Toast,避免重复显示 toastVC?.dismiss(animated: false) // 创建SwiftUI的Toast视图 let toastView = ToastView(message: message) .frame(maxWidth: .infinity, maxHeight: .infinity) // 把SwiftUI视图包装成UIViewController let hostingVC = UIHostingController(rootView: toastView) hostingVC.modalPresentationStyle = .overFullScreen hostingVC.modalTransitionStyle = .crossDissolve hostingVC.view.backgroundColor = .clear // 获取当前活跃的keyWindow if let window = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .map({$0 as? UIWindowScene}) .compactMap({$0}) .first?.windows .filter({$0.isKeyWindow}).first { window.rootViewController?.present(hostingVC, animated: true) { // 自动隐藏Toast DispatchQueue.main.asyncAfter(deadline: .now() + duration) { hostingVC.dismiss(animated: true) } } toastVC = hostingVC } } } // 自定义Toast的SwiftUI视图 struct ToastView: View { let message: String var body: some View { VStack { Spacer() Text(message) .padding(.horizontal, 20) .padding(.vertical, 12) .background(Color.black.opacity(0.8)) .foregroundColor(.white) .cornerRadius(12) .padding(.bottom, 60) } .background(Color.clear) } } // 在SwiftUI中调用全局Toast struct ContentView: View { @State private var isSheetPresented = false let toastManager = ToastManager.shared var body: some View { Button("打开Sheet") { isSheetPresented = true } .sheet(isPresented: $isSheetPresented) { Button("显示全局Toast") { toastManager.showToast(message: "我显示在Sheet上面啦!") } } } }
备注:内容来源于stack exchange,提问作者the middle
相关产品推荐
相关产品推荐

