美文网首页
iOS 列表cell曝光埋点

iOS 列表cell曝光埋点

作者: 隔壁班小明 | 来源:发表于2023-01-31 17:09 被阅读0次

    什么是曝光埋点,简单的说就是展示在屏幕上了,然后往服务端上传一个埋点。那列表cell的曝光埋点就是cell进入屏幕里面了。具体需求看下面
    一,需求:
    a.cell出现70%算出现在屏幕,当然这个可以改。
    b.cell在屏幕上停留一秒算是真的曝光(其实这个1s我并没有实现)

    二,心路历程:
    拿到一个新的需求怎么办当然是百度一下,结果网上都是列表停止滑动才开始算的。我们需求要求慢慢滑动cell在屏幕里超过1s也算。好家伙我直接没办法了。网上都是停止滑动这个时间点来做一个上报的时机,现在这个时机没有了怎么办。想了半天最后决定用一秒一次的定时器来实现。这样就导致我的停留一秒并不是很准确。(什么?为什么不缩短定时器间隔时间?太快了受不了~)

    三,方案:
    1,启动一个1s一次的定时器
    2,每次脉冲事件时获取屏幕中的cell列表
    3,用获取的列表和上一次的对比,这次有的上一次没有的是新增的,这次有的上一次也有的是在屏幕中停留的,这次没有的上一次有的是离开屏幕的
    4,保存新增的; 给屏幕中停留的记个时; 离开屏幕的计算一下停留时间是否大于1s,大于的话上报埋点。

    四,具体代码

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    @protocol KFZCollectionViewExposureDelegate;
    @interface KFZCollectionViewExposure : NSObject
    /** 停止处理 (一个页面多个列表切换需求隐藏的暂时不记录) */
    @property (nonatomic, assign) BOOL pause;
    /** 代理 */
    @property (nonatomic, weak) id<KFZCollectionViewExposureDelegate> delegate;
    -(instancetype)initWithCollectionView:(UICollectionView *)collectionView;
    
    @end
    
    @protocol KFZCollectionViewExposureDelegate <NSObject>
    //需要上报埋点
    -(void)needReportIndexPath:(NSIndexPath *)index exposure:(KFZCollectionViewExposure *)exposure;
    
    @end
    
    #import "KFZCollectionViewExposure.h"
    #import "TimerNotifier.h"
    #import "KFZExposureIndexPath.h"
    
    // 露出曝光 百分比
    CGFloat const ExposurePercentage = 0.8;
    
    @interface KFZCollectionViewExposure ()<TimerUserInterface>
    /** 监听的列表 */
    @property (nonatomic, strong) UICollectionView * collectionView;
    /** 缓存当前屏幕中显示的indexPath */
    @property (nonatomic, strong) NSMutableArray<KFZExposureIndexPath *> * showIndexPaths;
    /** 代理方法是否实现 */
    @property (nonatomic, assign) BOOL needReportSelector;
    @end
    
    @implementation KFZCollectionViewExposure
    
    -(instancetype)initWithCollectionView:(UICollectionView *)collectionView{
        self = [super init];
        if(self){
            _collectionView = collectionView;
            
            WS(weakSelf);
            [[TimerNotifier standard] registUser:weakSelf];
            weakSelf.allSeconds = -1;
            [TimerNotifier standard].timeInterval = 1;
        }
        return self;
    }
    
    -(void)timeDown{
        if(_pause){
            return;
        }
        
        dispatch_async(dispatch_get_main_queue(), ^{
            NSArray * curShowIndexPaths = [self getCalculateExposureIndexPaths];
            NSMutableArray * tmp = [self.showIndexPaths mutableCopy];
            NSMutableArray * newList = [[NSMutableArray alloc]init];
            for (NSIndexPath *indexPath in curShowIndexPaths) {
                BOOL find = NO;
                for (KFZExposureIndexPath * oldIndexPath in tmp) {
                    if([oldIndexPath.indexPath isEqual:indexPath]){
                        find = YES;
                        oldIndexPath.time += 1;
                        [tmp removeObject:oldIndexPath];
                        break;
                    }
                }
                if(!find){
                    KFZExposureIndexPath * newIndexPath = [[KFZExposureIndexPath alloc]init];
                    newIndexPath.indexPath = indexPath;
                    newIndexPath.time = 0;
                    [newList addObject:newIndexPath];
                }
            }
            [self.showIndexPaths removeObjectsInArray:tmp];
            [self.showIndexPaths addObjectsFromArray:newList];
            for (KFZExposureIndexPath * indexPath in tmp){
                if(indexPath.time >= 1){
                    if(self.needReportSelector){
                        [self.delegate needReportIndexPath:indexPath.indexPath exposure:self];
                    }
                }
            }
        });
    }
    
    
    #pragma mark - 重新计算当前区域曝光的IndexPath
    - (NSArray<NSIndexPath *> *)getCalculateExposureIndexPaths {
        __block NSMutableArray * array = [NSMutableArray array];
        NSArray<NSIndexPath *> * indexPathsForVisibleRows = self.collectionView.indexPathsForVisibleItems;
        
        [indexPathsForVisibleRows enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            if ([self calculateExposureForIndexPath:obj]) {
                [array addObject:obj];
            }
        }];
        return array;
    }
    
    //计算是否显示是否超过设置的百分比ExposurePercentage
    - (BOOL)calculateExposureForIndexPath:(NSIndexPath *)indexPath {
        CGRect previousCellRect = [self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath].frame;
        
        UIWindow * window = [self lastWindow];
        
        CGRect convertRect = [self.collectionView convertRect:previousCellRect toView:window];
        
        CGRect tabRect = CGRectIntersection([self.collectionView.superview convertRect:self.collectionView.frame toView:window], window.bounds);
        
        UICollectionViewFlowLayout * layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
        BOOL isFlowLayout = [layout isKindOfClass:[UICollectionViewFlowLayout class]];
        if (!isFlowLayout || (isFlowLayout && layout.scrollDirection == UICollectionViewScrollDirectionVertical)) {
            CGFloat currentTop = CGRectGetMinY(convertRect) - CGRectGetMinY(tabRect);
            if (currentTop < 0) {
                CGFloat percentage = (convertRect.size.height + currentTop) / convertRect.size.height;
                if (percentage >= ExposurePercentage) {
                    return YES;
                }
            } else {
                CGFloat currentBottom = CGRectGetMaxY(tabRect) - CGRectGetMaxY(convertRect);
                if (currentBottom < 0) {
                    CGFloat percentage = (convertRect.size.height + currentBottom) / convertRect.size.height;
                    if (percentage >= ExposurePercentage) {
                        return YES;
                    }
                } else {
                    return YES;
                }
            }
        } else {
            CGFloat currentLeft = CGRectGetMinX(convertRect) - CGRectGetMinX(tabRect);
            if (currentLeft < 0) {
                CGFloat percentage = (convertRect.size.width + currentLeft) / convertRect.size.width;
                if (percentage >= ExposurePercentage) {
                    return YES;
                }
            } else {
                CGFloat currentRight = CGRectGetMaxX(tabRect) - CGRectGetMaxX(convertRect);
                if (currentRight < 0) {
                    CGFloat percentage = (convertRect.size.width + currentRight) / convertRect.size.width;
                    if (percentage >= ExposurePercentage) {
                        return YES;
                    }
                } else {
                    return YES;
                }
            }
        }
        return NO;
    }
    
    - (UIWindow *)lastWindow{
        NSArray *windows = [UIApplication sharedApplication].windows;
        for (UIWindow *window in [windows reverseObjectEnumerator]) {
            if ([window isKindOfClass:[UIWindow class]] && CGRectEqualToRect(window.bounds, [UIScreen mainScreen].bounds)) {
                return window;
            }
        }
        return windows.lastObject;
    }
    
    #pragma mark - init
    
    -(void)setDelegate:(id<KFZCollectionViewExposureDelegate>)delegate{
        _delegate = delegate;
        _needReportSelector = [_delegate respondsToSelector:@selector(needReportIndexPath:exposure:)];
    }
    
    - (NSMutableArray<KFZExposureIndexPath *> *)showIndexPaths{
        if(nil == _showIndexPaths){
            _showIndexPaths = [[NSMutableArray alloc]init];
        }
        return _showIndexPaths;
    }
    
    #pragma mark - timer
    
    @synthesize allSeconds;
    -(void)receivedTimerUpData:(NSString *)timeString{
        [self timeDown];
    }
    
    @end
    

    其中TimerNotifier是我之前写的定时器https://www.jianshu.com/p/9d6e67ffbbd8。KFZExposureIndexPath是个小的保存时间和index的类。代码如下

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface KFZExposureIndexPath : NSObject
    /** 位置 */
    @property (nonatomic, strong) NSIndexPath * indexPath;
    /** 曝光时间 */
    @property (nonatomic, assign) NSInteger time;
    @end
    
    NS_ASSUME_NONNULL_END
    

    五,具体使用:

    #import "KFZCollectionViewExposure.h"
    
    <KFZCollectionViewExposureDelegate>
    
    @property (nonatomic, strong) KFZCollectionViewExposure * exposureTool;
    
    _exposureTool = [[KFZCollectionViewExposure alloc]initWithCollectionView:_collectionView];
    _exposureTool.delegate = self;
    
    #pragma mark - KFZCollectionViewExposureDelegate
    //需要上报埋点
    -(void)needReportIndexPath:(NSIndexPath *)index exposure:(KFZCollectionViewExposure *)exposure{
        
    }
    

    OK,打完收工~~~

    相关文章

      网友评论

          本文标题:iOS 列表cell曝光埋点

          本文链接:https://www.haomeiwen.com/subject/oppjmrtx.html