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

SwiftUI:如何让Toast视图显示在Sheet弹窗上方?

解决SwiftUI中Toast覆盖Sheet的问题

问题根源

SwiftUI的sheet是当前视图的模态弹窗,视图层级高于当前视图本身。你之前将Toast的Modifier附加在MainScreen上,导致Toast与MainScreen处于同一层级,自然会被层级更高的Sheet覆盖。

解决方案:将Toast置于全局最顶层(基于UIWindow实现)

通过UIKit的UIWindow承载Toast,设置其窗口层级高于系统弹窗,确保Toast能覆盖所有视图包括Sheet。

1. 重构Toast管理器与视图

创建全局Toast管理器,控制Toast的显示/隐藏:

import SwiftUI
import Combine

class SharedToastManager: ObservableObject {
    static let shared = SharedToastManager() // 单例实现
    @Published var isShowingToast = false
    @Published var toastMessage = ""
    
    private var cancellables = Set<AnyCancellable>()
    
    private init() {}
    
    func showToast(message: String) {
        toastMessage = message
        isShowingToast = true
        // 2秒后自动隐藏Toast
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.isShowingToast = false
        }
    }
}

struct ToastView: View {
    @ObservedObject var manager = SharedToastManager.shared
    
    var body: some View {
        if manager.isShowingToast {
            VStack {
                Spacer()
                Text(manager.toastMessage)
                    .padding(.horizontal, 20)
                    .padding(.vertical, 12)
                    .background(Color.black.opacity(0.8))
                    .foregroundColor(.white)
                    .cornerRadius(8)
                    .transition(.opacity.combined(with: .scale))
                Spacer().frame(height: 40)
            }
            .animation(.easeInOut, value: manager.isShowingToast)
            .edgesIgnoringSafeArea(.all)
        }
    }
}

2. 创建全局Toast窗口管理类

import UIKit

class ToastWindow: UIWindow {
    static let shared = ToastWindow()
    
    private override init(frame: CGRect) {
        super.init(frame: frame)
        setupWindow()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupWindow() {
        windowLevel = UIWindow.Level.alert + 1 // 层级高于系统弹窗
        backgroundColor = .clear
        rootViewController = UIHostingController(rootView: ToastView())
        isHidden = true
    }
    
    func show() {
        isHidden = false
    }
    
    func hide() {
        isHidden = true
    }
}

3. 关联管理器与窗口状态

在SharedToastManager中添加监听,自动控制Toast窗口显示:

class SharedToastManager: ObservableObject {
    // ... 原有代码 ...
    
    init() {
        $isShowingToast.sink { isShowing in
            if isShowing {
                ToastWindow.shared.show()
            } else {
                ToastWindow.shared.hide()
            }
        }.store(in: &cancellables)
    }
    
    // ... 原有代码 ...
}

4. 初始化Toast窗口

修改App入口代码,启动时初始化Toast窗口:

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    init() {
        ToastWindow.shared.frame = UIScreen.main.bounds
    }
    
    var body: some Scene {
        WindowGroup {
            MainScreen()
        }
    }
}

5. 在Sheet中调用Toast

修改MainScreen,在Sheet内触发Toast显示:

struct MainScreen: View {
    @State var isShowing = false
    let toastManager = SharedToastManager.shared
    
    var body: some View {
        NavigationView {
            VStack (spacing: 22) {
                Button {
                    isShowing = true
                } label: {
                    Text("Show sheet")
                }
            }
        }
        .sheet(isPresented: $isShowing) {
            VStack {
                Text("This is a Sheet")
                Button("Show Toast") {
                    toastManager.showToast(message: "Toast覆盖Sheet啦!")
                }
                .padding(.top, 20)
            }
            .padding()
        }
    }
}

轻量替代方案(基于SwiftUI原生API)

如果不想依赖UIKit,可通过fullScreenCover实现,将Toast附加到全局最外层:

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    @StateObject var toastManager = SharedToastManager.shared
    
    var body: some Scene {
        WindowGroup {
            MainScreen()
                .fullScreenCover(isPresented: $toastManager.isShowingToast) {
                    ToastView()
                        .background(Color.clear)
                        .allowsHitTesting(false) // 允许点击穿透到下层视图
                }
        }
    }
}

此方案利用fullScreenCover的层级优势,无需额外UIWindow配置,适合轻量场景。


内容的提问来源于stack exchange,提问作者Elias Al Zaghrini

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 09:45:29