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

Swift开发中视图不在窗口层级的警告问题求助

解决“Attempt to present... whose view is not in the window hierarchy!”警告

这个警告我太熟悉了!问题根源在于你调用present的时机不对——在viewDidLoad方法执行时,HomeVC的视图还没有完全被添加到应用的窗口视图层级里,这时候强行弹出LoginVC就会触发这个警告。虽然功能看起来正常,但底层其实存在视图层级未就绪的问题,得调整代码的执行时机。

给你两个可靠的解决方案:

方案一:将登录状态检查移到viewDidAppear方法中

viewDidAppear是视图完全显示在屏幕上、已经成功加入窗口层级之后才会触发的方法,在这里执行present操作就不会有层级问题了。不过要注意,每次视图出现都会触发这个方法,所以建议配合移除监听的操作避免重复监听:

import UIKit
import Firebase
class HomeVC: UIViewController {
    var authHandle: AuthStateDidChangeListenerHandle?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print("user enter homeVC")
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // 添加登录状态监听
        authHandle = Auth.auth().addStateDidChangeListener { (auth, user) in
            if user == nil {
                guard let loginVC = self.storyboard?.instantiateViewController(withIdentifier: "login") as? LoginVC else { return }
                self.present(loginVC, animated: true, completion: nil)
            }
        }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // 移除监听,避免内存泄漏和重复触发
        if let handle = authHandle {
            Auth.auth().removeStateDidChangeListener(handle)
        }
    }
}

方案二:在回调中延迟执行present操作

如果还是想保留在viewDidLoad里添加监听,可以用DispatchQueue.main.async把present代码放到主队列的下一个运行循环中,等HomeVC的视图完全挂载到窗口层级后再执行:

import UIKit
import Firebase
class HomeVC: UIViewController {
    var authHandle: AuthStateDidChangeListenerHandle?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 添加登录状态监听
        authHandle = Auth.auth().addStateDidChangeListener { (auth, user) in
            if user == nil {
                // 延迟到下一个主队列循环执行present
                DispatchQueue.main.async {
                    guard let loginVC = self.storyboard?.instantiateViewController(withIdentifier: "login") as? LoginVC else { return }
                    self.present(loginVC, animated: true, completion: nil)
                }
            }
        }
        print("user enter homeVC")
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // 移除监听
        if let handle = authHandle {
            Auth.auth().removeStateDidChangeListener(handle)
        }
    }
}

另外补充个小建议:尽量用guard let代替强制解包as!,这样可以避免因Storyboard ID错误导致的崩溃,代码会更健壮。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:39:24