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

实现iOS应用全页面离线模式功能的技术求助

全局离线状态处理方案修复

问题分析

你在AppDelegate里的实现存在几个核心问题:

  1. 视图层级错误:直接用addSubview(offlineVC.view)把视图加到window上,没有触发OfflineViewController的完整生命周期,也不符合iOS控制器的层级管理逻辑,导致功能失效。
  2. iOS 13+场景适配缺失:iOS 13及以后,窗口由UIScene管理,AppDelegate里的window可能为空,直接通过appDelegate?.window无法获取正确的窗口实例。
  3. 线程安全问题:Reachability的回调在后台线程执行,直接操作UI会引发异常,必须切换到主线程处理。
  4. 重复监听问题:OfflineViewController里再次启动Reachability监听,会导致重复回调,逻辑混乱。

解决方案(无第三方库)

我们通过单例网络管理器统一监听网络状态,再通过获取当前顶层ViewController的方式弹出离线页面,同时适配iOS 13+的场景架构。

步骤1:创建全局网络监听单例

直接基于系统SystemConfiguration框架实现,完全不需要第三方库:

import UIKit
import SystemConfiguration

// 网络状态枚举
enum NetworkStatus {
    case reachable
    case unreachable
}

class NetworkManager: NSObject {
    static let shared = NetworkManager()
    private var reachability: SCNetworkReachability?
    private var currentStatus: NetworkStatus = .reachable
    
    private override init() {
        super.init()
        setupReachability()
    }
    
    // 初始化网络监听
    private func setupReachability() {
        var zeroAddress = sockaddr_in()
        zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
        zeroAddress.sin_family = sa_family_t(AF_INET)
        
        guard let reachability = withUnsafePointer(to: &zeroAddress, {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, $0)
            }
        }) else { return }
        
        self.reachability = reachability
        var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
        context.info = Unmanaged.passUnretained(self).toOpaque()
        
        // 设置状态变化回调
        SCNetworkReachabilitySetCallback(reachability, { _, flags, info in
            guard let info = info else { return }
            let manager = Unmanaged<NetworkManager>.fromOpaque(info).takeUnretainedValue()
            manager.updateStatus(with: flags)
        }, &context)
        
        // 将监听加入主运行循环
        SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetCurrent(), CFRunLoopMode.commonModes.rawValue)
    }
    
    // 更新网络状态并发送通知
    private func updateStatus(with flags: SCNetworkReachabilityFlags) {
        let isReachable = flags.contains(.reachable)
        let needsConnection = flags.contains(.connectionRequired)
        let canConnectAutomatically = flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic)
        let canConnectWithoutUser = canConnectAutomatically && !flags.contains(.interventionRequired)
        
        let newStatus: NetworkStatus = (isReachable && (!needsConnection || canConnectWithoutUser)) ? .reachable : .unreachable
        
        if newStatus != currentStatus {
            currentStatus = newStatus
            NotificationCenter.default.post(name: NSNotification.Name("NetworkStatusChanged"), object: currentStatus)
        }
    }
    
    // 获取当前网络状态
    func getCurrentStatus() -> NetworkStatus {
        return currentStatus
    }
}

步骤2:全局监听网络状态并弹出离线页面

iOS13+推荐:SceneDelegate实现

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
        
        // 注册网络状态变化通知
        NotificationCenter.default.addObserver(self, selector: #selector(networkStatusChanged(_:)), name: NSNotification.Name("NetworkStatusChanged"), object: nil)
        
        // 初始化网络管理器
        _ = NetworkManager.shared
    }
    
    @objc private func networkStatusChanged(_ notification: Notification) {
        guard let status = notification.object as? NetworkStatus else { return }
        
        // 切到主线程操作UI
        DispatchQueue.main.async {
            if status == .unreachable {
                // 避免重复弹出离线页面
                if self.topViewController() is OfflineViewController {
                    return
                }
                let storyboard = UIStoryboard(name: "Main", bundle: nil)
                let offlineVC = storyboard.instantiateViewController(withIdentifier: "OfflineViewController") as! OfflineViewController
                offlineVC.modalPresentationStyle = .fullScreen
                self.topViewController()?.present(offlineVC, animated: true)
            }
        }
    }
    
    // 获取当前顶层ViewController
    private func topViewController() -> UIViewController? {
        guard let rootVC = window?.rootViewController else { return nil }
        return getTopVC(from: rootVC)
    }
    
    private func getTopVC(from vc: UIViewController) -> UIViewController? {
        if let navVC = vc as? UINavigationController {
            return getTopVC(from: navVC.visibleViewController ?? navVC)
        } else if let tabVC = vc as? UITabBarController {
            return getTopVC(from: tabVC.selectedViewController ?? tabVC)
        } else if let presentedVC = vc.presentedViewController {
            return getTopVC(from: presentedVC)
        } else {
            return vc
        }
    }
}

iOS12及以下:AppDelegate适配

如果需要支持iOS12及以下,在AppDelegate中添加如下逻辑:

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 注册网络状态变化通知
        NotificationCenter.default.addObserver(self, selector: #selector(networkStatusChanged(_:)), name: NSNotification.Name("NetworkStatusChanged"), object: nil)
        
        // 初始化网络管理器
        _ = NetworkManager.shared
        
        return true
    }
    
    @objc private func networkStatusChanged(_ notification: Notification) {
        guard let status = notification.object as? NetworkStatus else { return }
        
        DispatchQueue.main.async {
            if status == .unreachable {
                if self.topViewController() is OfflineViewController {
                    return
                }
                let storyboard = UIStoryboard(name: "Main", bundle: nil)
                let offlineVC = storyboard.instantiateViewController(withIdentifier: "OfflineViewController") as! OfflineViewController
                offlineVC.modalPresentationStyle = .fullScreen
                self.topViewController()?.present(offlineVC, animated: true)
            }
        }
    }
    
    private func topViewController() -> UIViewController? {
        guard let rootVC = window?.rootViewController else { return nil }
        return getTopVC(from: rootVC)
    }
    
    private func getTopVC(from vc: UIViewController) -> UIViewController? {
        if let navVC = vc as? UINavigationController {
            return getTopVC(from: navVC.visibleViewController ?? navVC)
        } else if let tabVC = vc as? UITabBarController {
            return getTopVC(from: tabVC.selectedViewController ?? tabVC)
        } else if let presentedVC = vc.presentedViewController {
            return getTopVC(from: presentedVC)
        } else {
            return vc
        }
    }
    
    // 其余SceneSession生命周期代码保留
}

步骤3:修改OfflineViewController

去掉重复的Reachability监听,改用通知或直接检查网络状态:

import UIKit

class OfflineViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // 监听网络恢复通知,自动关闭页面
        NotificationCenter.default.addObserver(self, selector: #selector(onNetworkReachable), name: NSNotification.Name("NetworkStatusChanged"), object: nil)
    }
    
    @IBAction func tapTryAgain(_ sender: Any) {
        checkNetworkAndDismiss()
    }
    
    @objc private func onNetworkReachable(_ notification: Notification) {
        guard let status = notification.object as? NetworkStatus else { return }
        if status == .reachable {
            dismiss(animated: true)
        }
    }
    
    private func checkNetworkAndDismiss() {
        if NetworkManager.shared.getCurrentStatus() == .reachable {
            dismiss(animated: true)
        } else {
            // 提示用户仍无网络
            let alert = UIAlertController(title: "提示", message: "当前仍无网络连接", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "确定", style: .default))
            present(alert, animated: true)
        }
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

步骤4:清理原代码

删除首页ViewController中所有Reachability相关代码,现在由全局管理器统一处理。

关键注意事项

  • 避免重复弹出:弹出离线页面前检查当前顶层VC是否已经是OfflineViewController,防止重复弹窗。
  • 主线程操作:所有UI操作必须在主线程执行,Reachability回调默认在后台线程,必须切换线程。
  • 内存管理:使用NotificationCenter时要在deinit中移除监听,避免内存泄漏。
  • 无第三方依赖:全程仅使用系统框架,完全符合你的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 16:05:34