日常需求开发中, 经常会遇到cell完全显示在屏幕上时做一些业务逻辑的需求,比如曝光埋点。
UICollectionView代理的并没有完全满足要求的回调方法。
- (void)collectionView:(UICollectionView*)collectionView willDisplayCell:(UICollectionViewCell*)cell forItemAtIndexPath:(NSIndexPath*)indexPath
上面方法是在cell被添加的时候回调,此时cell并没有显示在屏幕上,而且如果用户不继续滑,有可能cell不会显示。
所以可以利用分类实现这样的功能,以满足需求。
tableview如果要实现同样的功能的话道理一样。可以照猫画虎。
具体实现如下
UICollectionView+DispalyCell.h 文件
// 方案:
// 首先通过 代理collectionView:willDisplayCell:forItemAtIndexPath:收集将要显示的cell,添加到一个set内,
// 然后在scrollViewDidScroll: 回调方法内循环遍历set内的cell, 计算cell是否已全部显示在给定的区域内
// 如果已经全是显示,则回调代理, 并移除该cell, 移除后的cell可再次添加到数组里。
//
// 注意:
// 1:操作set时的线程安全问题。
// 2:快速滑动时的性能问题
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol CollectionViewDisplayDelegate <NSObject>
- (void)collectionView:(UICollectionView*)collectionView didDisplayCell:(UICollectionViewCell*)cell forItemAtIndexPath:(NSIndexPath*)indexPath;
@end
@interface UICollectionView (DispalyCell)
@property(nonatomic, weak) id<CollectionViewDisplayDelegate> displayDelegate;
@property(nonatomic, strong, readonly) NSMutableSet* cells; // cells that will be displayed
- (void)willDisplayCell:(UICollectionViewCell *)cell;
- (void)displayCell;
@end
NS_ASSUME_NONNULL_END
UICollectionView+DisplayCell.m 文件
#import "UICollectionView+DisplayCell.h"
#import <objc/runtime.h>
staticchar*k_cells ="k_cells";
staticchar*k_displayDelegate ="k_displayDelegate";
@implementation UICollectionView (DispalyCell)
- (void)setDisplayDelegate:(id<CollectionViewDisplayDelegate>)displayDelegate{
objc_setAssociatedObject(self, k_displayDelegate, displayDelegate, OBJC_ASSOCIATION_ASSIGN);
}
- (id<CollectionViewDisplayDelegate>)displayDelegate{
return objc_getAssociatedObject(self, k_displayDelegate);
}
- (NSMutableSet *)cells{
NSMutableSet *set = objc_getAssociatedObject(self, k_cells);
if(!set) {
objc_setAssociatedObject(self, k_cells, [NSMutableSet set], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}else{
returnset;
}
return objc_getAssociatedObject(self, k_cells);
}
- (void)willDisplayCell:(UICollectionViewCell *)cell{
[self.cells addObject:cell];
}
- (void)displayCell{
[self.cells enumerateObjectsUsingBlock:^(id _Nonnullobj,BOOL*_Nonnullstop) {
UICollectionViewCell*cell = obj;
CGRect cell_r = [self convertRect:cell.frame toView:nil]; //nil 表示转换到window上
CGRect collection_r = [self.superview convertRect:self.frame toView:nil];
if(CGRectContainsRect(collection_r, cell_r)) {
[self.cells removeObject:cell];
SEL selector = @selector(collectionView:didDisplayCell:forItemAtIndexPath:);
if([self.displayDelegate respondsToSelector:selector]) {
[self.displayDelegate collectionView:self didDisplayCell:cell forItemAtIndexPath:[self indexPathForCell:cell]];
}
}
}];
}
@end
下面是应用:
step1: 设置代理, 就像设置 delegate 或 dataSource一样。
self.collectionView.displayDelegate = self;
step2: 在下面的代理方法中调用分类的 willDisplayCell 方法。
- (void)collectionView:(UICollectionView*)collectionView willDisplayCell:(UICollectionViewCell*)cell forItemAtIndexPath:(NSIndexPath*)indexPath{
[collectionView willDisplayCell:cell];
}
step3: 然后在scrollViewDidScroll中调用displayCell。
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
// 这里做一下过滤, 也可以不过滤, 视具体情况而定
if(self.collectionView == scrollView) {
[self.collectionView displayCell];
}
}
step4: 实现回调
- (void)collectionView:(UICollectionView*)collectionView didDisplayCell:(UICollectionViewCell*)cell forItemAtIndexPath:(NSIndexPath*)indexPath{
// 在这里处理业务逻辑
}