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