[关闭]
@chinese-ppmt 2018-04-16T05:16:26.000000Z 字数 3942 阅读 1706

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

runtime


一、前言

最近在解答[最新版]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对象(如下图:);

    componet和scrollView相互关系

  3. PPMJRefreshComponent观察的结果怎么传递给UIScrollView?我采用的是delegate(PPMJRefreshComponentDelegate),需要UIScrollView对象遵守;(此处不适用block是因为block嵌套block容易出问题)
    PPMJRefreshComponentDelegate.png
  4. UIScrollView对象实现代理,并设置scrollBlock的时候触发监听:(代码如下,注释已写进去)

  1. @implementation UIScrollView (ScrollBlock)
  2. #pragma mark --- PPMJRefreshComponentDelegate
  3. -(void)scrollViewContentOffsetDidChange:(NSDictionary<NSKeyValueChangeKey,id> *)change{
  4. [self contentOffsetBlockAction:change];
  5. }
  6. -(void)scrollViewPanStateDidChange:(NSDictionary<NSKeyValueChangeKey,id> *)change{
  7. [self panGestureRecognizerStateAction:change];
  8. }
  9. -(void)contentOffsetBlockAction:(NSDictionary<NSKeyValueChangeKey,id> *)change
  10. {
  11. //这个属性字面理解意思为:正在拖动。实际上是:scrollView是否滚动了,只要不是最开始初始化的时候设置的位置,就为YES。
  12. if (!self.isDragging) {
  13. return;
  14. }
  15. //【注意】此处要特别注意,如果设置contentInset的话,要给pp_lastContentOffsetY赋值为insetT的初始值
  16. if (!self.pp_lastContentOffsetY) {
  17. [self setupInitializeOffsetY];
  18. }
  19. //获取当前的contentOffsetY
  20. CGFloat currentContentOffsetY = self.pp_FSB_offsetY;
  21. //如果前后的contentOffsetY值相同,就不做处理
  22. CGFloat lastContentOffsetY = [self.pp_lastContentOffsetY floatValue];
  23. if (currentContentOffsetY == lastContentOffsetY) {
  24. return;
  25. }
  26. //是否是向上滑,初始值为NO
  27. BOOL isToUp = NO;
  28. //向上滑动
  29. if (currentContentOffsetY > lastContentOffsetY) {
  30. //处理滑动到底部,继续上滑后系统自动反弹而重复调用的情况
  31. if (currentContentOffsetY+self.pp_h > self.pp_FSB_contentH) {
  32. return;
  33. }
  34. isToUp = YES;
  35. }else{
  36. //向下滑动
  37. //处理已经最上面了仍然下拉而反弹时,反复调用
  38. if (currentContentOffsetY <= self.pp_FSB_insetT) {
  39. return;
  40. }
  41. }
  42. //给pp_lastContentOffsetY绑定值
  43. objc_setAssociatedObject(self, @selector(pp_lastContentOffsetY), [NSNumber numberWithFloat:currentContentOffsetY], OBJC_ASSOCIATION_RETAIN);
  44. //是否超过一个屏幕
  45. BOOL isInOneScreen = (self.pp_FSB_insetT+self.pp_FSB_contentH <= self.pp_h);
  46. if (self.pp_scrollBlock) {
  47. self.pp_scrollBlock(currentContentOffsetY, isToUp,isInOneScreen);
  48. }
  49. }
  50. -(void)panGestureRecognizerStateAction:(NSDictionary<NSKeyValueChangeKey,id> *)change
  51. {
  52. if (self.panGestureRecognizer.state == UIGestureRecognizerStateEnded) {
  53. //内容不够一个屏幕时,系统会自动回弹,这时候记得把pp_lastContentOffsetY重新设置一下
  54. if (self.pp_FSB_insetT+self.pp_FSB_contentH <= self.pp_h) {
  55. [self setupInitializeOffsetY];
  56. }else{
  57. //超过一个屏幕,这时候下拉,当松开的时候要把pp_lastContentOffsetY重新设置一下
  58. if (self.pp_FSB_offsetY < self.pp_FSB_insetT) {
  59. [self setupInitializeOffsetY];
  60. }
  61. }
  62. }
  63. }
  64. #pragma mark --- 初始化contentOffsetY的值
  65. -(void)setupInitializeOffsetY{
  66. CGFloat currentContentOffsetY = -self.pp_FSB_insetT;
  67. objc_setAssociatedObject(self, @selector(pp_lastContentOffsetY), [NSNumber numberWithFloat:currentContentOffsetY], OBJC_ASSOCIATION_RETAIN);
  68. }
  69. -(void)setPp_scrollBlock:(PPUIScrollViewScrollBlock)pp_scrollBlock
  70. {
  71. //在设置scrollBlock的时候,触发监听
  72. self.pp_component.delegate = self;
  73. objc_setAssociatedObject(self, @selector(pp_scrollBlock), pp_scrollBlock, OBJC_ASSOCIATION_RETAIN);
  74. }
  75. -(PPUIScrollViewScrollBlock)pp_scrollBlock
  76. {
  77. return objc_getAssociatedObject(self, _cmd);
  78. }
  79. @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,此刻吃着零食喝着饮料,匆匆结文。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注