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

登录后切换ViewController时保持OverlayLoadingView置顶显示的问题

问题:登录后保持加载Overlay在最上层,同时完成ViewController跳转

登录成功后,我希望展示自定义NIB文件的OverlayLoadingView来显示加载状态并同步加载数据,之后通过以下代码跳转到另一个Storyboard的ViewController:

self.present(vc, animated: true, completion: nil)

我打算把OverlayLoadingView设计成单例,在新的ViewController中隐藏它,但跳转时Overlay会消失。请问有没有办法让这个加载视图始终置于所有视图之上,同时在后台完成新ViewController的跳转?

期望流程:LoginViewController→登录成功→显示OverlayView→present MainViewController(位于OverlayView下方)→加载初始数据→加载完成后隐藏OverlayView。

相关实现代码

OverlayLoadingView 代码

@IBDesignable public class OverlayLoadingView: UIView { 
    @IBOutlet weak var headerImageView: UIImageView! 
    @IBOutlet weak var detailTextLabel: UILabel! 
    
    class var sharedInstance: OverlayLoadingView { 
        struct Static { 
            static let instance: OverlayLoadingView = OverlayLoadingView(frame: UIScreen.main.bounds) 
        } 
        return Static.instance 
    } 
    
    // Custom view from the XIB file 
    fileprivate var view: UIView! 
    
    /** Initialiser method */ 
    override init(frame: CGRect) { 
        super.init(frame: frame) 
        setupNib() 
    } 
    
    /** Initialiser method */ 
    required public init?(coder aDecoder: NSCoder) { 
        super.init(coder: aDecoder) 
        setupNib() 
    } 
    
    fileprivate func loadNib() -> UIView? { 
        return Bundle.main.loadNibNamed("OverlayLoadingView", owner: self, options: nil)?.first as? UIView 
    } 
    
    func setupNib() { 
        if let overlayLoadingView = loadNib() { 
            self.view = overlayLoadingView 
            let screenWidth = Util.screenSize().width 
            let screenHeight = Util.screenSize().height 
            self.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight) 
            view.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight) 
            self.addSubview(view) 
        } 
    } 
    
    public func showOverlay() { 
        if let appDelegate = UIApplication.shared.delegate as? AppDelegate, let window = appDelegate.window { 
            self.alpha = 1.0 
            window.addSubview(self) 
            activityIndicator.startAnimating() 
        } 
    } 
    
    public func hideOverlayView() { 
        activityIndicator.stopAnimating() 
        self.removeFromSuperview() 
    } 
}

显示Overlay的代码

DispatchQueue.main.async { 
    let storyboard = UIStoryboard(name: "TabMenu", bundle: nil) 
    let vc = storyboard.instantiateInitialViewController()! 
    OverlayLoadingView.sharedInstance.showOverlay() 
    self.present(vc, animated: true, completion: { 
        // Dismiss current VC? 
    }) 
}

视图层级要求:Overlay视图需置于所有视图之上。


解决方案

嘿,我来帮你搞定这个问题~ 你遇到的核心问题是:当你present新的ViewController时,iOS系统会自动把新VC的视图放到window的最上层,把你之前添加的Overlay给盖住了。不过只要调整一下Overlay的层级管理逻辑,就能让它一直保持在所有视图的最前面。

1. 修复Overlay的显示逻辑,强制锁定最上层

首先修改OverlayLoadingView里的showOverlay方法,我们要做两件事:一是避免重复添加Overlay到window,二是每次显示时都强制把它移到window的最上层:

public func showOverlay() {
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let window = appDelegate.window else { return }
    
    self.alpha = 1.0
    // 先检查Overlay是否已经在window上,防止重复添加
    if self.superview != window {
        window.addSubview(self)
    }
    // 强制将Overlay移到所有视图的最上层
    window.bringSubviewToFront(self)
    // 更新frame适配屏幕(比如旋转后的情况)
    self.frame = window.bounds
    self.view.frame = window.bounds
    
    activityIndicator.startAnimating()
}

2. 调整跳转后的逻辑,确保Overlay持续显示

在你跳转MainViewController的代码里,需要在present完成后,再次确认Overlay的层级,同时把数据加载的逻辑放到completion里,这样就能完美贴合你的期望流程:

DispatchQueue.main.async { 
    let storyboard = UIStoryboard(name: "TabMenu", bundle: nil) 
    guard let mainVC = storyboard.instantiateInitialViewController() as? MainViewController else { return }
    
    let overlay = OverlayLoadingView.sharedInstance
    overlay.showOverlay() 
    
    // 跳转新VC
    self.present(mainVC, animated: true, completion: { 
        // 跳转完成后,再次把Overlay移到最上层(防止动画过程中层级被打乱)
        overlay.showOverlay()
        
        // 开始加载MainVC的初始数据
        mainVC.loadInitialData { [weak self] success in
            // 加载完成后隐藏Overlay
            overlay.hideOverlayView()
            
            // 如果需要,这里可以处理加载结果
            if !success {
                // 比如提示加载失败
            }
        }
    }) 
}

这里假设你的MainViewController有一个loadInitialData(completion:)方法,用来异步加载初始数据,这样就能在加载完成后再隐藏Overlay,完全符合你的流程。

3. 优化单例和内存管理(可选)

你的单例实现没问题,但可以加个小优化:在hideOverlayView里,添加渐变动画后再移除视图,体验更流畅:

public func hideOverlayView() {
    activityIndicator.stopAnimating()
    UIView.animate(withDuration: 0.3) {
        self.alpha = 0.0
    } completion: { _ in
        self.removeFromSuperview()
    }
}

为什么之前的代码会让Overlay消失?

因为当你调用present时,iOS会把新VC的视图添加到当前window的最上层,这就导致你的Overlay被新VC的视图盖住了。而我们通过window.bringSubviewToFront(self),每次都强制把Overlay放到window的subviews数组的最后一位(也就是最上层),这样不管有多少个VC被present,Overlay都会一直显示在最前面。

按照上面的修改,就能完美实现你想要的流程:登录成功→显示Overlay→present MainVC(在Overlay下方)→加载数据→加载完成隐藏Overlay。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:17:35