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

SwiftUI:如何让Activity Indicator覆盖含导航栏的整个屏幕

解决全屏覆盖导航栏的Activity Indicator问题

问题核心是:默认视图修饰符添加的Activity Indicator处于当前视图(B)的层级,而导航栏属于NavigationStack的上层容器,所以无法覆盖。下面给两种可行的实现方式:

方式一:利用窗口层级添加全局Indicator

这种方式直接把Indicator加到系统窗口层级,能覆盖整个屏幕(包括导航栏、状态栏),不用调整现有视图结构。

先调整你的Indicator实现和修饰符:

import SwiftUI

struct ActivityIndicator: View {
    let isAnimating: Bool
    let style: UIActivityIndicatorView.Style
    
    var body: some View {
        if isAnimating {
            UIActivityIndicatorView(style: style)
                .onAppear { $0.startAnimating() }
                .onDisappear { $0.stopAnimating() }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.black.opacity(0.5))
        }
    }
}

struct FullScreenActivityIndicator: ViewModifier {
    @Binding var isPresented: Bool
    
    func body(content: Content) -> some View {
        content
            .onChange(of: isPresented) { newValue in
                // 获取当前活跃的主窗口
                guard let window = UIApplication.shared.connectedScenes
                        .filter({ $0.activationState == .foregroundActive })
                        .compactMap({ $0 as? UIWindowScene })
                        .first?.windows
                        .first(where: { $0.isKeyWindow }) else { return }
                
                if newValue {
                    // 创建Indicator的宿主控制器并加到窗口
                    let indicatorHost = UIHostingController(rootView: ActivityIndicator(isAnimating: true, style: .large))
                    indicatorHost.view.frame = window.bounds
                    indicatorHost.view.backgroundColor = .black.withAlphaComponent(0.5)
                    window.addSubview(indicatorHost.view)
                } else {
                    // 移除窗口中的Indicator视图
                    window.subviews.forEach { subview in
                        if subview is UIHostingController<ActivityIndicator>.View {
                            subview.removeFromSuperview()
                        }
                    }
                }
            }
    }
}

// 给View加扩展,方便调用
extension View {
    func fullScreenActivityIndicator(isPresented: Binding<Bool>) -> some View {
        self.modifier(FullScreenActivityIndicator(isPresented: isPresented))
    }
}

// 视图B的使用示例
struct ViewB: View {
    @State private var isLoading = false
    
    var body: some View {
        VStack {
            Text("视图B内容")
            Button("开始加载") {
                isLoading = true
                // 模拟加载任务
                DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                    isLoading = false
                }
            }
        }
        .fullScreenActivityIndicator(isPresented: $isLoading)
    }
}

方式二:在NavigationStack层级添加Overlay

这种更符合SwiftUI的声明式风格,把Indicator放在导航栈的顶层Overlay里,通过环境变量控制显示状态:

  1. 先定义环境键,用来传递加载状态:
struct LoadingKey: EnvironmentKey {
    static let defaultValue: Binding<Bool> = .constant(false)
}

extension EnvironmentValues {
    var isLoading: Binding<Bool> {
        get { self[LoadingKey.self] }
        set { self[LoadingKey.self] = newValue }
    }
}
  1. 在包裹导航栈的根视图添加Overlay:
struct MainContentView: View {
    @State private var isLoading = false
    
    var body: some View {
        NavigationStack {
            ViewA() // 视图A,里面有跳转到ViewB的逻辑
        }
        .environment(\.isLoading, $isLoading)
        .overlay {
            if isLoading {
                ActivityIndicator(isAnimating: true, style: .large)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(Color.black.opacity(0.5))
                    .ignoresSafeArea()
            }
        }
    }
}
  1. 视图B里直接用环境变量控制加载:
struct ViewB: View {
    @Environment(\.isLoading) private var isLoading
    
    var body: some View {
        VStack {
            Text("视图B内容")
            Button("开始加载") {
                isLoading.wrappedValue = true
                DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                    isLoading.wrappedValue = false
                }
            }
        }
    }
}

两种方式对比

  • 方式一不用改动导航栈结构,适合快速实现单个页面的全屏加载,但依赖UIKit的窗口API,后续SwiftUI版本更新可能需要微调。
  • 方式二更适配SwiftUI的设计逻辑,适合项目中有多个页面需要全屏加载的场景,通过环境变量统一管理加载状态更规范。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 09:32:54