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

如何监听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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 03:45:39