Swift:在AppDelegate中获取当前UIViewController并处理断网UI问题
First off, great job getting your Reachability setup working for detecting network state changes! The problem you’re hitting with fetching the current view controller likely stems from your existing code not accounting for all possible view controller hierarchy scenarios—like tab bar controllers nested with navigation controllers, or modally presented view controllers.
Let’s break down what’s missing in your current code:
- It only handles direct navigation controllers as the root, but if your root is a
UITabBarControlleror a view controller has been presented modally, your code won’t catch the actual visible screen. - The optional binding for
windowis a bit fragile, sinceUIApplication.shared.delegate?.windowreturns an optional of an optional (UIWindow??).
Step 1: Create a Helper to Get the Topmost View Controller
Add this extension to UIApplication (you can drop it in a new Swift file or at the bottom of your AppDelegate.swift) to reliably fetch the current visible view controller, no matter the hierarchy:
extension UIApplication { func topMostViewController() -> UIViewController? { // Grab the active key window first guard let window = self.windows.first(where: { $0.isKeyWindow }) else { return nil } var topViewController = window.rootViewController // Traverse through any modally presented view controllers while let presentedViewController = topViewController?.presentedViewController { topViewController = presentedViewController } // Handle navigation controllers if let navigationController = topViewController as? UINavigationController { topViewController = navigationController.visibleViewController } // Handle tab bar controllers (and nested nav controllers inside tabs) else if let tabBarController = topViewController as? UITabBarController { topViewController = tabBarController.selectedViewController if let tabNavVC = topViewController as? UINavigationController { topViewController = tabNavVC.visibleViewController } } return topViewController } }
Step 2: Update Your Reachability Callback
Now modify your reachabilityChanged function to use this helper, then update the UI of the top view controller to notify the user:
@objc func reachabilityChanged(note: Notification) { let reachability = note.object as! Reachability if reachability.connection == .none { // Get the current visible view controller guard let currentVC = UIApplication.shared.topMostViewController() else { return } print("Current visible VC: \(currentVC)") // Example: Add a network error banner to the current VC's view let errorBanner = UIView(frame: CGRect(x: 0, y: -60, width: currentVC.view.frame.width, height: 60)) errorBanner.backgroundColor = .systemRed errorBanner.tag = 999 // Use a tag to avoid duplicate banners // Remove existing banner if it's already there currentVC.view.viewWithTag(999)?.removeFromSuperview() let errorLabel = UILabel(frame: errorBanner.bounds) errorLabel.text = "Network connection lost!" errorLabel.textColor = .white errorLabel.textAlignment = .center errorBanner.addSubview(errorLabel) currentVC.view.addSubview(errorBanner) // Animate the banner into view UIView.animate(withDuration: 0.3) { errorBanner.frame.origin.y = 0 } } else { // Optional: Remove the error banner when network is restored if let currentVC = UIApplication.shared.topMostViewController() { currentVC.view.viewWithTag(999)?.removeFromSuperview() } } }
Why This Works
- The
topMostViewControllerhelper first targets the key window (the one actively visible to the user), then traverses through any modally presented screens to get the frontmost one. - It handles both
UINavigationControllerandUITabBarControllerhierarchies, including nested cases where a tab’s root is a navigation controller. - The UI update example adds a temporary banner, but you can replace this with your own custom alert, toast, or any UI change you need to notify the user.
内容的提问来源于stack exchange,提问作者Ahmed

