You need to enable JavaScript to run this app. 如何无需在各viewcontroller的viewwillappear写代码,一次性监听页面跳转与导航栈变化
优惠活动
大模型
产品
解决方案
定价
更多

如何无需在各viewcontroller的viewwillappear写代码,一次性监听页面跳转与导航栈变化

全局监听iOS页面导航与导航栈变更(无需逐页埋点)

实现逻辑

完全不需要在每个UIViewController的viewWillAppear/viewDidAppear里写重复监听代码,核心是通过Runtime方法交换统一拦截系统导航相关方法,一次注入全局生效:

  • 拦截所有UIViewController的展示生命周期,捕获每一个页面的展示事件
  • 拦截UINavigationController的核心导航方法,捕捉导航栈的任意变动(push、pop、批量重置栈)
  • 所有事件统一通过对外回调抛出,支持自定义过滤规则,排除系统类控制器、弹窗等非目标页面

核心实现

所有逻辑收敛在独立的监听模块中,完全不侵入业务代码:

1. 方法交换基础配置

对UIViewController和UINavigationController的系统方法做一次性替换,保留原有方法执行逻辑,不影响原有业务功能:

import UIKit

// UIViewController 生命周期拦截
extension UIViewController {
    static let swizzleLifecycle: Void = {
        let original = #selector(UIViewController.viewDidAppear(_:))
        let swizzled = #selector(UIViewController.monitor_viewDidAppear(_:))
        guard let originalMethod = class_getInstanceMethod(UIViewController.self, original),
              let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzled) else { return }
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }()

    @objc private func monitor_viewDidAppear(_ animated: Bool) {
        // 先执行原有生命周期逻辑
        self.monitor_viewDidAppear(animated)
        // 过滤后上报页面展示事件
        if let validVC = NavigationMonitor.shared.validTargetVC(self) {
            NavigationMonitor.shared.didShowPage(validVC)
        }
    }
}

// UINavigationController 导航栈方法拦截
extension UINavigationController {
    static let swizzleNavActions: Void = {
        // 拦截push方法
        let pushOriginal = #selector(UINavigationController.pushViewController(_:animated:))
        let pushSwizzled = #selector(UINavigationController.monitor_push(_:animated:))
        guard let pushOri = class_getInstanceMethod(UINavigationController.self, pushOriginal),
              let pushSwz = class_getInstanceMethod(UINavigationController.self, pushSwizzled) else { return }
        method_exchangeImplementations(pushOri, pushSwz)

        // 拦截pop方法
        let popOriginal = #selector(UINavigationController.popViewController(animated:))
        let popSwizzled = #selector(UINavigationController.monitor_pop(animated:))
        guard let popOri = class_getInstanceMethod(UINavigationController.self, popOriginal),
              let popSwz = class_getInstanceMethod(UINavigationController.self, popSwizzled) else { return }
        method_exchangeImplementations(popOri, popSwz)

        // 拦截批量修改导航栈的方法
        let setOriginal = #selector(UINavigationController.setViewControllers(_:animated:))
        let setSwizzled = #selector(UINavigationController.monitor_setStack(_:animated:))
        guard let setOri = class_getInstanceMethod(UINavigationController.self, setOriginal),
              let setSwz = class_getInstanceMethod(UINavigationController.self, setSwizzled) else { return }
        method_exchangeImplementations(setOri, setSwz)
    }()

    @objc private func monitor_push(_ vc: UIViewController, animated: Bool) {
        self.monitor_push(vc, animated: animated)
        NavigationMonitor.shared.didChangeStack(currentStack: self.viewControllers, action: "push")
    }

    @objc private func monitor_pop(animated: Bool) -> UIViewController? {
        let poppedVC = self.monitor_pop(animated: animated)
        NavigationMonitor.shared.didChangeStack(currentStack: self.viewControllers, action: "pop")
        return poppedVC
    }

    @objc private func monitor_setStack(_ vcs: [UIViewController], animated: Bool) {
        self.monitor_setStack(vcs, animated: animated)
        NavigationMonitor.shared.didChangeStack(currentStack: self.viewControllers, action: "reset")
    }
}

2. 全局监听单例

统一管理事件上报、过滤规则,对外暴露事件回调:

class NavigationMonitor {
    static let shared = NavigationMonitor()
    /// 业务侧可绑定该回调,接收所有导航事件
    var onNavigationEvent: ((NavigationEvent) -> Void)?

    /// 仅需在APP启动时调用一次,全局生效
    func start() {
        UIViewController.swizzleLifecycle
        UINavigationController.swizzleNavActions
    }

    /// 自定义过滤规则:排除系统类、容器类控制器,仅保留业务页面
    func validTargetVC(_ vc: UIViewController) -> UIViewController? {
        let systemPrefix = ["UI", "_UI", "AV", "CA"]
        let vcName = NSStringFromClass(vc.classForCoder)
        let isSystemVC = systemPrefix.contains { vcName.hasPrefix($0) }
        guard !isSystemVC,
              !(vc is UINavigationController),
              !(vc is UITabBarController) else {
            return nil
        }
        return vc
    }

    fileprivate func didShowPage(_ vc: UIViewController) {
        let event = NavigationEvent(
            type: .pageShow,
            targetPage: NSStringFromClass(vc.classForCoder),
            stackAction: nil,
            currentStack: nil,
            time: Date().timeIntervalSince1970
        )
        onNavigationEvent?(event)
    }

    fileprivate func didChangeStack(currentStack: [UIViewController], action: String) {
        let stackNames = currentStack.map { NSStringFromClass($0.classForCoder) }
        let event = NavigationEvent(
            type: .stackChange,
            targetPage: nil,
            stackAction: action,
            currentStack: stackNames,
            time: Date().timeIntervalSince1970
        )
        onNavigationEvent?(event)
    }
}

/// 导航事件模型
struct NavigationEvent {
    enum EventType {
        case pageShow
        case stackChange
    }
    let type: EventType
    let targetPage: String?
    let stackAction: String?
    let currentStack: [String]?
    let time: TimeInterval
}

使用说明

  • 仅需在AppDelegate的didFinishLaunchingWithOptions方法中执行一次NavigationMonitor.shared.start(),后续所有页面跳转、导航栈变动都会自动被捕获
  • 无需在任何业务UIViewController中添加监听代码,完全无侵入
  • 可根据业务需求调整validTargetVC的过滤规则,比如需要监听弹窗、系统页面跳转时,移除对应过滤条件即可
  • 无第三方依赖,基于系统Runtime能力实现,兼容全版本iOS系统

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.05 18:45:02