如何无需在各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
相关产品推荐
相关产品推荐

