什么是曝光埋點,簡單的說就是展示在屏幕上了,然后往服務(wù)端上傳一個埋點。那列表cell的曝光埋點就是cell進(jìn)入屏幕里面了。具體需求看下面
一,需求:
a.cell出現(xiàn)70%算出現(xiàn)在屏幕,當(dāng)然這個可以改。
b.cell在屏幕上停留一秒算是真的曝光(其實這個1s我并沒有實現(xiàn))
二,心路歷程:
拿到一個新的需求怎么辦當(dāng)然是百度一下,結(jié)果網(wǎng)上都是列表停止滑動才開始算的。我們需求要求慢慢滑動cell在屏幕里超過1s也算。好家伙我直接沒辦法了。網(wǎng)上都是停止滑動這個時間點來做一個上報的時機(jī),現(xiàn)在這個時機(jī)沒有了怎么辦。想了半天最后決定用一秒一次的定時器來實現(xiàn)。這樣就導(dǎo)致我的停留一秒并不是很準(zhǔn)確。(什么?為什么不縮短定時器間隔時間?太快了受不了~)
三,方案:
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>
/** 監(jiān)聽的列表 */
@property (nonatomic, strong) UICollectionView * collectionView;
/** 緩存當(dāng)前屏幕中顯示的indexPath */
@property (nonatomic, strong) NSMutableArray<KFZExposureIndexPath *> * showIndexPaths;
/** 代理方法是否實現(xiàn) */
@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 - 重新計算當(dāng)前區(qū)域曝光的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;
}
//計算是否顯示是否超過設(shè)置的百分比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是我之前寫的定時器http://www.itdecent.cn/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,打完收工~~~