iOS 列表cell曝光埋点

什么是曝光埋点,简单的说就是展示在屏幕上了,然后往服务端上传一个埋点。那列表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,打完收工~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容