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

SwiftUI modal Sheet遮挡ProgressView 如何实现全局加载控件常驻顶层

解决方案

问题根源

SwiftUI 中的 .sheet、.alert、fullScreenCover 等系统浮层,是渲染在应用最高层级的独立 UIWindow 或 UIViewController 层级上,你在普通内容视图的 ZStack 中添加的加载视图,哪怕设置 zIndex 也仅在当前视图树生效,无法覆盖系统浮层。

最优兼容方案(支持 iOS 13+)

直接将全局进度条挂载到应用最顶层的 UIWindow 上,该层级高于所有系统默认浮层,可保证进度条始终处于最顶部。

步骤1:定义全局状态管理与工具方法

import SwiftUI
import UIKit

// 全局加载状态管理器
class GlobalLoadingManager: ObservableObject {
    static let shared = GlobalLoadingManager()
    @Published var isShowing: Bool = false
    private init() {}
}

// 获取当前顶层Window
extension UIApplication {
    func topMostWindow() -> UIWindow? {
        connectedScenes
            .filter { $0.activationState == .foregroundActive }
            .compactMap { $0 as? UIWindowScene }
            .first?.windows
            .last { $0.isKeyWindow }
    }
}

// 全局调用方法
func showGlobalLoading() {
    GlobalLoadingManager.shared.isShowing = true
}

func hideGlobalLoading() {
    GlobalLoadingManager.shared.isShowing = false
}

步骤2:定义全局加载视图

// 全局加载视图
struct GlobalLoadingView: View {
    @ObservedObject private var manager = GlobalLoadingManager.shared
    
    var body: some View {
        if manager.isShowing {
            ZStack(alignment: .center) {
                // 半透明背景遮罩,不需要可以删掉
                Color.black.opacity(0.3)
                    .ignoresSafeArea()
                
                VStack(spacing: 16) {
                    ActivityIndicator()
                    Text("Loading...")
                        .foregroundColor(.white)
                        .font(.system(size: 14))
                }
                .padding(24)
                .background(Color.black.opacity(0.7))
                .cornerRadius(8)
            }
        }
    }
}

// 保留你原有的UIKit桥接指示器
struct ActivityIndicator: UIViewRepresentable {
    typealias UIView = UIActivityIndicatorView
    fileprivate var configuration = { (_: UIView) in }

    func makeUIView(context: Context) -> UIView { 
        let indicator = UIView(style: .large)
        indicator.color = .white
        return indicator
    }
    func updateUIView(_ uiView: UIView, context: Context) {
        uiView.startAnimating()
        configuration(uiView)
    }
}

步骤3:挂载到顶层Window

在 AppDelegate 的 didFinishLaunchingWithOptions 方法中添加挂载逻辑(如果是 SwiftUI 生命周期的项目,通过 UIApplicationDelegateAdaptor 绑定自定义 AppDelegate 即可):

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // 原有初始化逻辑保留
    
    // 挂载全局加载视图
    guard let topWindow = UIApplication.shared.topMostWindow() else { return true }
    let loadingHost = UIHostingController(rootView: GlobalLoadingView())
    loadingHost.view.backgroundColor = .clear
    // 设为true时加载期间会拦截下层用户交互,不需要可改为false
    loadingHost.view.isUserInteractionEnabled = true
    topWindow.addSubview(loadingHost.view)
    
    // 全屏约束适配
    loadingHost.view.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        loadingHost.view.topAnchor.constraint(equalTo: topWindow.topAnchor),
        loadingHost.view.leadingAnchor.constraint(equalTo: topWindow.leadingAnchor),
        loadingHost.view.trailingAnchor.constraint(equalTo: topWindow.trailingAnchor),
        loadingHost.view.bottomAnchor.constraint(equalTo: topWindow.bottomAnchor)
    ])
    return true
}

用法说明

你可以在任意位置调用全局方法控制加载状态:

  • 显示:showGlobalLoading()
  • 隐藏:hideGlobalLoading()

原来包裹 ContentView 的 LoadingView 可以直接删除,不需要再在视图树中嵌套。

注意事项

  • 该方案对所有系统浮层都生效,包括 sheet、alert、弹窗、甚至系统级别浮层
  • 如果需要适配 iPad 多窗口、外接屏等多场景,可以自行调整 topMostWindow 的获取逻辑
  • 加载视图的样式可以根据需求自行调整,替换为 iOS14+ 的 ProgressView 也可以正常运行

内容的提问来源于stack exchange,提问作者Valerio

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 14:27:03