浅读MJRefresh后自定义了个UIScrollView监听滚动的block属性

一、前言

最近在解答[最新版]MJRefresh解析与详细使用指导和MJRefresh实现刷新(使用它的Block方法)中简友的提问,浅读了下MJRefresh的源码(关于源码解读,网上已有很多,我后续也会写一篇我自己的解读,不过今天要说的是:借鉴别人的思路,做or完善自己的事。):利用KVO在- (void)willMoveToSuperview:(UIView *)newSuperview方法调用时监听scrollView的contentOffset/contentSize和panGestureRecognizer的state属性,然后做对应操作

二、开发困惑

通常,作为iOS开发人员,判断UIScrollView/UITableView/UICollectionView的滚动情况的事,时有发生。如果每次都去实现delegate方法,在我看来,有些麻烦。除了一遍一遍的写代理,还有一种就是建个基类,但是这样基类还是要实现对应的delegate方法。

三、解决方法:给UIScrollView添加block属性监听滚动

先预览下效果(上面红色的是手机录屏所致)

效果图

四、理清思路

  1. 新建一个类PPMJRefreshComponent,类似MJRefresh中的MJRefreshComponent,用来当做观察者;
  2. 既然PPMJRefreshComponent要观察UIScrollViewcontentOffset以及panGestureRecognizerstate,那么PPMJRefreshComponent就要关联当前的UIScrollView;并且,UIScrollView要拥有一个PPMJRefreshComponent对象(如下图:);
浅读MJRefresh后自定义了个UIScrollView监听滚动的block属性_第1张图片
componet和scrollView相互关系
  1. PPMJRefreshComponent观察的结果怎么传递给UIScrollView?我采用的是delegate(PPMJRefreshComponentDelegate),需要UIScrollView对象遵守;(此处不使用block是因为block嵌套block容易出问题)
浅读MJRefresh后自定义了个UIScrollView监听滚动的block属性_第2张图片
PPMJRefreshComponentDelegate.png
  1. UIScrollView对象实现代理,并设置scrollBlock的时候触发监听:(代码如下,注释已写进去)
@implementation UIScrollView (ScrollBlock)

#pragma mark --- PPMJRefreshComponentDelegate
-(void)scrollViewContentOffsetDidChange:(NSDictionary *)change{
     [self contentOffsetBlockAction:change];
}
-(void)scrollViewPanStateDidChange:(NSDictionary *)change{
    [self panGestureRecognizerStateAction:change];
}

-(void)contentOffsetBlockAction:(NSDictionary *)change
{
    //这个属性字面理解意思为:正在拖动。实际上是:scrollView是否滚动了,只要不是最开始初始化的时候设置的位置,就为YES。
    if (!self.isDragging) {
        return;
    }
    //【注意】此处要特别注意,如果设置contentInset的话,要给pp_lastContentOffsetY赋值为insetT的初始值
    if (!self.pp_lastContentOffsetY) {
        [self setupInitializeOffsetY];
    }
    //获取当前的contentOffsetY
    CGFloat currentContentOffsetY = self.pp_FSB_offsetY;
   
    //如果前后的contentOffsetY值相同,就不做处理
    CGFloat lastContentOffsetY = [self.pp_lastContentOffsetY floatValue];
    if (currentContentOffsetY == lastContentOffsetY) {
        return;
    }

    //是否是向上滑,初始值为NO
    BOOL 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 *)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 *)change这个方法处理滑动情况,但是开始下拉上拉到底的两种临街状态时的pp_lastContentOffsetY需要特殊处理,而这个处理就放在panGestureRecognizer.state == UIGestureRecognizerStateEnded的时候。

最后,感谢MJRefresh

文字无法描述这个过程,当时怎么想,做的时候怎么做,后来又是怎么调整的,说多了,就失去了文章的核心,所以:感兴趣的最好看下代码,有不懂的请问我,尽我之力,一起学习。

2018-03-08 14:20:40 妇女节快乐!感谢公司的party,此刻吃着零食喝着饮料,匆匆结文。

你可能感兴趣的:(浅读MJRefresh后自定义了个UIScrollView监听滚动的block属性)