Swift:通过AppDelegate添加子视图至UI顶层失败求助
解决AppDelegate中添加顶层视图不显示及主队列无限循环问题
看起来你遇到的问题主要集中在顶层视图无法显示和主队列调用导致无限循环崩溃两个点上,我来帮你逐一排查解决:
一、为什么视图不显示?
1. iOS 13+ 下 keyWindow 获取错误
从iOS 13开始,苹果引入了UIScene架构,UIApplication.shared.keyWindow已经不再可靠,因为一个APP可能有多个场景(比如iPad的分屏)。你需要获取当前活跃场景的keyWindow:
// 新增一个获取当前活跃window的方法 private func getCurrentActiveWindow() -> UIWindow? { if #available(iOS 13.0, *) { return UIApplication.shared.connectedScenes .filter { $0.activationState == .foregroundActive } .compactMap { $0 as? UIWindowScene } .first?.windows .first { $0.isKeyWindow } } else { return UIApplication.shared.keyWindow } }
2. 视图初始化方式错误
你提到要从xib加载NetworkErrorView,但代码里却用了frame初始化,这会导致xib里的布局和控件都没有被加载出来。你需要给NetworkErrorView添加一个从xib加载的类方法:
// 在NetworkErrorView类中添加 class func loadFromXib() -> NetworkErrorView { return Bundle.main.loadNibNamed("NetworkErrorView", owner: nil, options: nil)?.first as! NetworkErrorView }
然后替换原来的初始化代码:
networkErrorView = NetworkErrorView.loadFromXib() // 再设置frame guard let errorView = networkErrorView, let window = getCurrentActiveWindow() else { return } errorView.frame = CGRect(x: 30, y: window.frame.height / 4, width: window.frame.width - 60, height: window.frame.height / 2)
二、为什么用DispatchQueue.main.async会无限循环崩溃?
1. 循环引用问题
你的reachabilityChanged方法中如果直接引用self,再加上主队列的闭包,很容易形成循环引用,导致内存无法释放,最终崩溃。需要用weak self打破循环:
2. 重复添加视图触发通知
当你每次收到无网络通知时都创建新视图并添加,可能会触发某些网络相关的操作(比如视图内部的重试逻辑),导致可达性通知反复触发,加上没有判断视图是否已存在,就会进入无限循环。
修复后的完整代码
// 先定义全局变量 var networkErrorView: NetworkErrorView? func reachabilityChanged(note: Notification) { guard let reachability = note.object as? Reachability else { return } // 确保UI操作在主线程,同时用weak self避免循环引用 DispatchQueue.main.async { [weak self] in guard let self = self else { return } let currentWindow = self.getCurrentActiveWindow() if reachability.isReachable { print("APP: Network reachable") // 移除视图后置空,避免重复引用 self.networkErrorView?.removeFromSuperview() self.networkErrorView = nil } else { print("ERROR: Network not reachable") // 先判断视图是否已存在,避免重复添加 guard self.networkErrorView == nil else { print("View already showing") return } self.networkErrorView = NetworkErrorView.loadFromXib() guard let errorView = self.networkErrorView, let window = currentWindow else { return } errorView.frame = CGRect(x: 30, y: window.frame.height / 4, width: window.frame.width - 60, height: window.frame.height / 2) window.addSubview(errorView) window.bringSubview(toFront: errorView) print("View is showing") } } } // 获取当前活跃window的方法 private func getCurrentActiveWindow() -> UIWindow? { if #available(iOS 13.0, *) { return UIApplication.shared.connectedScenes .filter { $0.activationState == .foregroundActive } .compactMap { $0 as? UIWindowScene } .first?.windows .first { $0.isKeyWindow } } else { return UIApplication.shared.keyWindow } }
额外检查点
- 确认你的可达性通知只注册了一次,避免重复触发回调;
- 检查
NetworkErrorView的xib文件名和类名是否一致,避免加载失败; - 可以给
NetworkErrorView设置一个背景色(比如红色),方便测试视图是否真的被添加。
内容的提问来源于stack exchange,提问作者user1888564
相关产品推荐
相关产品推荐

