如何监听UIViewController展示事件?分析框架页面转场检测方法
全局监听ViewController展示事件的实现方案
要实现全局监听ViewController的展示、计算特定VC的展示时长,无需修改每一处present/push调用,常用且高效的方案是Method Swizzling(方法交换),下面是具体实现思路和代码:
核心思路
通过交换UIViewController(及UINavigationController)的关键方法,在不侵入原有业务代码的前提下,注入自定义的跟踪逻辑:
- 交换
presentViewController:animated:completion:,监听模态弹出的VC - 交换
pushViewController:animated:,监听导航栈推入的VC - 交换
viewDidAppear:/viewDidDisappear:,准确获取VC的展示/消失时机,计算时长
具体代码实现
1. UIViewController分类:处理模态与展示时长跟踪
创建UIViewController+Tracking分类:
#import <UIKit/UIKit.h> #import <objc/runtime.h> @implementation UIViewController (Tracking) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 交换presentViewController方法 [self swizzleOriginalSelector:@selector(presentViewController:animated:completion:) withSwizzledSelector:@selector(swizzled_presentViewController:animated:completion:)]; // 交换viewDidAppear方法 [self swizzleOriginalSelector:@selector(viewDidAppear:) withSwizzledSelector:@selector(swizzled_viewDidAppear:)]; // 交换viewDidDisappear方法 [self swizzleOriginalSelector:@selector(viewDidDisappear:) withSwizzledSelector:@selector(swizzled_viewDidDisappear:)]; }); } /// 通用方法交换工具 + (void)swizzleOriginalSelector:(SEL)originalSelector withSwizzledSelector:(SEL)swizzledSelector { Method originalMethod = class_getInstanceMethod(self, originalSelector); Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector); BOOL didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } /// 交换后的present方法 - (void)swizzled_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion { // 先执行原present逻辑 [self swizzled_presentViewController:viewControllerToPresent animated:flag completion:^{ if (completion) completion(); // 注入跟踪逻辑 [self startTrackingForVC:viewControllerToPresent]; }]; } /// 交换后的viewDidAppear:处理导航栈推入的VC(因为push没有completion回调) - (void)swizzled_viewDidAppear:(BOOL)animated { [self swizzled_viewDidAppear:animated]; // 导航栈推入的VC在这里触发跟踪(避免重复触发模态的情况) if (!self.presentingViewController) { [self startTrackingForVC:self]; } } /// 交换后的viewDidDisappear:计算展示时长 - (void)swizzled_viewDidDisappear:(BOOL)animated { [self swizzled_viewDidDisappear:animated]; // 获取记录的开始时间 NSNumber *startTime = objc_getAssociatedObject(self, _cmd); if (startTime) { NSTimeInterval duration = CACurrentMediaTime() - startTime.doubleValue; // 检查是否为目标VC,执行额外操作 if ([self.restorationIdentifier isEqualToString:@"TargetVC"]) { NSLog(@"目标VC展示时长:%.2f秒", duration); // 这里可以添加上报统计、业务逻辑等操作 } // 清除关联数据 objc_setAssociatedObject(self, _cmd, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } /// 开始跟踪VC - (void)startTrackingForVC:(UIViewController *)vc { if ([vc.restorationIdentifier isEqualToString:@"TargetVC"]) { // 记录展示开始时间(用关联对象存储,避免添加属性) objc_setAssociatedObject(vc, @selector(swizzled_viewDidDisappear:), @(CACurrentMediaTime()), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } @end
2. UINavigationController分类:处理导航栈推入
创建UINavigationController+Tracking分类,确保push的VC也能被监听:
#import <UIKit/UIKit.h> #import <objc/runtime.h> @implementation UINavigationController (Tracking) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self swizzleOriginalSelector:@selector(pushViewController:animated:) withSwizzledSelector:@selector(swizzled_pushViewController:animated:)]; }); } + (void)swizzleOriginalSelector:(SEL)originalSelector withSwizzledSelector:(SEL)swizzledSelector { Method originalMethod = class_getInstanceMethod(self, originalSelector); Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector); BOOL didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } - (void)swizzled_pushViewController:(UIViewController *)viewController animated:(BOOL)animated { [self swizzled_pushViewController:viewController animated:animated]; // 标记即将被push的VC,避免在viewDidAppear中重复处理 objc_setAssociatedObject(viewController, @"isPushedByNav", @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end
注意事项
- Method Swizzling必须在
+load方法中执行,用dispatch_once保证仅交换一次,避免重复操作。 - 视图标识符建议使用
restorationIdentifier,或给VC添加自定义属性标识,确保能准确识别目标VC。 - 如果项目中存在自定义的
present/push封装方法,需要针对性交换对应的自定义方法。 - 注意与第三方库的方法交换冲突,可通过检查方法实现是否被修改来规避。
关于你提到的NotificationCenter/Darwin通知
系统并没有默认发送ViewController展示/消失的通知,你之前的记忆可能是项目中自行埋点发送的通知,而非系统原生支持。目前全局监听最可靠的方案还是Method Swizzling。
内容的提问来源于stack exchange,提问作者Ghean Ginanjar Adiguna
相关产品推荐
相关产品推荐

