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

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

一、前言

最近在解答[最新版]MJRefresh解析与详细使用指导MJRefresh实现刷新(使用它的Block方法)中简友的提问,浅读了下 MJRefresh 的源码 (关于源码解读,网上已有很多,我后续也会写一篇我自己的解读,不过今天要说的是:借鉴别人的思路,做or完善自己的事。):

利用KVO- (void)willMoveToSuperview:(UIView *)newSuperview方法调用时监听scrollViewcontentOffset/contentSizepanGestureRecognizerstate属性,然后做对应操作。

二、开发困惑

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

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

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

四、理清思路

  1. 新建一个类 PPMJRefreshComponent,类似MJRefresh中的MJRefreshComponent,用来当做观察者;
  2. 既然 PPMJRefreshComponent 要观察 UIScrollViewcontentOffset以及panGestureRecognizerstate,那么 PPMJRefreshComponent 就要关联当前的 UIScrollView;并且,UIScrollView 要拥有一个 PPMJRefreshComponent 对象(如下图:);

componet和scrollView相互关系

  1. PPMJRefreshComponent 观察的结果怎么传递给 UIScrollView?我采用的是delegate(PPMJRefreshComponentDelegate),需要 UIScrollView 对象遵守;(此处不使用block是因为block嵌套block容易出问题)

PPMJRefreshComponentDelegate

  1. UIScrollView 对象实现代理,并设置scrollBlock的时候触发监听:(代码如下,注释已写进去)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@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];
    }
    //获取当前的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<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,此刻 吃着零食喝着饮料,匆匆结文。

Built with Hugo
主题 StackJimmy 设计