登录后切换ViewController时保持OverlayLoadingView置顶显示的问题
登录成功后,我希望展示自定义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

