iOS 列表cell曝光埋点

2023-01-31  本文已影响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,打完收工~~~

上一篇下一篇

猜你喜欢

热点阅读