@chinese-ppmt
2018-04-16T05:16:26.000000Z
字数 3942
阅读 1891
runtime
最近在解答[最新版]MJRefresh解析与详细使用指导和MJRefresh实现刷新(使用它的Block方法)中简友的提问,浅读了下MJRefresh的源码
(关于源码解读,网上已有很多,我后续也会写一篇我自己的解读,不过今天要说的是:借鉴别人的思路,做or完善自己的事。):利用KVO在- (void)willMoveToSuperview:(UIView *)newSuperview方法调用时监听scrollView的contentOffset/contentSize和panGestureRecognizer的state属性,然后做对应操作。
通常,作为iOS开发人员,判断UIScrollView/UITableView/UICollectionView的滚动情况的事,时有发生。如果每次都去实现delegate方法,在我看来,有些麻烦。除了一遍一遍的写代理,还有一种就是建个基类,但是这样基类还是要实现对应的delegate方法。
先预览下效果
(上面红色的是手机录屏所致):
- 新建一个类
PPMJRefreshComponent,类似MJRefresh中的MJRefreshComponent,用来当做观察者;- 既然
PPMJRefreshComponent要观察UIScrollView的contentOffset以及panGestureRecognizer的state,那么PPMJRefreshComponent就要关联当前的UIScrollView;并且,UIScrollView要拥有一个PPMJRefreshComponent对象(如下图:);
PPMJRefreshComponent观察的结果怎么传递给UIScrollView?我采用的是delegate(PPMJRefreshComponentDelegate),需要UIScrollView对象遵守;(此处不适用block是因为block嵌套block容易出问题)
UIScrollView对象实现代理,并设置scrollBlock的时候触发监听:(代码如下,注释已写进去)
@implementation UIScrollView (ScrollBlock)#pragma mark --- PPMJRefreshComponentDelegate-(void)scrollViewContentOffsetDidChange:(NSDictionary<NSKeyValueChangeKey,id> *)change{[self contentOffsetBlockAction:change];}-(void)scrollViewPanStateDidChange:(NSDictionary<NSKeyValueChangeKey,id> *)change{[self panGestureRecognizerStateAction:change];}-(void)contentOffsetBlockAction:(NSDictionary<NSKeyValueChangeKey,id> *)change{//这个属性字面理解意思为:正在拖动。实际上是:scrollView是否滚动了,只要不是最开始初始化的时候设置的位置,就为YES。if (!self.isDragging) {return;}//【注意】此处要特别注意,如果设置contentInset的话,要给pp_lastContentOffsetY赋值为insetT的初始值if (!self.pp_lastContentOffsetY) {[self setupInitializeOffsetY];}//获取当前的contentOffsetYCGFloat currentContentOffsetY = self.pp_FSB_offsetY;//如果前后的contentOffsetY值相同,就不做处理CGFloat lastContentOffsetY = [self.pp_lastContentOffsetY floatValue];if (currentContentOffsetY == lastContentOffsetY) {return;}//是否是向上滑,初始值为NOBOOL isToUp = NO;//向上滑动if (currentContentOffsetY > lastContentOffsetY) {//处理滑动到底部,继续上滑后系统自动反弹而重复调用的情况if (currentContentOffsetY+self.pp_h > self.pp_FSB_contentH) {return;}isToUp = YES;}else{//向下滑动//处理已经最上面了仍然下拉而反弹时,反复调用if (currentContentOffsetY <= self.pp_FSB_insetT) {return;}}//给pp_lastContentOffsetY绑定值objc_setAssociatedObject(self, @selector(pp_lastContentOffsetY), [NSNumber numberWithFloat:currentContentOffsetY], OBJC_ASSOCIATION_RETAIN);//是否超过一个屏幕BOOL isInOneScreen = (self.pp_FSB_insetT+self.pp_FSB_contentH <= self.pp_h);if (self.pp_scrollBlock) {self.pp_scrollBlock(currentContentOffsetY, isToUp,isInOneScreen);}}-(void)panGestureRecognizerStateAction:(NSDictionary<NSKeyValueChangeKey,id> *)change{if (self.panGestureRecognizer.state == UIGestureRecognizerStateEnded) {//内容不够一个屏幕时,系统会自动回弹,这时候记得把pp_lastContentOffsetY重新设置一下if (self.pp_FSB_insetT+self.pp_FSB_contentH <= self.pp_h) {[self setupInitializeOffsetY];}else{//超过一个屏幕,这时候下拉,当松开的时候要把pp_lastContentOffsetY重新设置一下if (self.pp_FSB_offsetY < self.pp_FSB_insetT) {[self setupInitializeOffsetY];}}}}#pragma mark --- 初始化contentOffsetY的值-(void)setupInitializeOffsetY{CGFloat currentContentOffsetY = -self.pp_FSB_insetT;objc_setAssociatedObject(self, @selector(pp_lastContentOffsetY), [NSNumber numberWithFloat:currentContentOffsetY], OBJC_ASSOCIATION_RETAIN);}-(void)setPp_scrollBlock:(PPUIScrollViewScrollBlock)pp_scrollBlock{//在设置scrollBlock的时候,触发监听self.pp_component.delegate = self;objc_setAssociatedObject(self, @selector(pp_scrollBlock), pp_scrollBlock, OBJC_ASSOCIATION_RETAIN);}-(PPUIScrollViewScrollBlock)pp_scrollBlock{return objc_getAssociatedObject(self, _cmd);}@end
针对上面的代码补充说明如下:
1. 注意component的初识与关联,一定要弄懂为啥我代码中要用runtime强制关联;
2. 注意pp_lastContentOffsetY的使用,它是给UIScrollView动态绑定的记录上一次的contentOffsetY值的,只有在滑动的时候有效,最终如果你放外部的话,偏移量还是和contentOffset.Y的值一样。
3. -(void)contentOffsetBlockAction:(NSDictionary<NSKeyValueChangeKey,id> *)change这个方法处理滑动情况,但是开始下拉和上拉到底的两种临街状态时的pp_lastContentOffsetY需要特殊处理,而这个处理就放在panGestureRecognizer.state == UIGestureRecognizerStateEnded的时候。
最后,感谢MJRefresh!
文字无法描述这个过程,当时怎么想,做的时候怎么做,后来又是怎么调整的,说多了,就失去了文章的核心,所以:感兴趣的最好看下代码,有不懂的请问我,尽我之力,一起学习。
2018-03-08 14:20:40 妇女节快乐!感谢公司的party,此刻吃着零食喝着饮料,匆匆结文。