MJRefresh记录

一天

背景

写这篇文章主要有两个目的:

  1. 虽然refresh的源码已经有很多小伙伴分析过了。但是其应用不能说不够广阔。所以,抽空重新梳理了一下该源码,然后记录一下自己的心得。方便之后重新阅读,然后对比更新。
  2. 另一个问题是发现一个阅读源码的现象:比如源码有100个文件,当阅读了10个文件,就说我阅读过某某某框架的源码,然后看到源码中某某方法和另一方法实现一致,然后就直接调用同一个方法。但是此时就很容易出现问题。有时和你理论的时候,依旧意识不到问题所在,继续翻开源码说实现是一致的。可源码整体看一下,就能发现遇到的问题。最后一种情况将会分析这遇到的问题。

本文从以下几个方面进行记录:

  • 继承层次
  • 整体分析
  • 各类的职责
  • 下拉刷新
    • 主动调用代码
    • UI进行交互
  • 上拉刷新
  • 常见的错误❌

一、基本类继承层次关系

// 为了方便表示,使用~代替
~ = MJRefresh

~Component: UIView
    ~Header
        ~StateHeader
            ~NormalHeader
            ~GifHeader
    ~Footer
        ~AutoFooter
            ~AutoStateFooter
                ~AutoNormalFooter   
                ~AutoGifFooter
        ~BackFooter 
            ~BackStateFooter
                ~BackNormalFooter
                ~BackGifFooter

二、整体分析:

  1. MJRefreshComponet是继承自UIView。其他所有的类都是其子类。
  2. mj_header和mj_footer是添加在ScrollView上分类的属性,其类型是Header/Footer类型,并且其添加到了scrollview的view容器内。因此在赋值后,才会出现在头部/尾部。
  3. 通篇控制的方式,是通过子类重写其基类的MJRefreshState类型的state属性,重写其set方法进行更改当前刷新的状态。从而控制头部和尾部的展示内容(是否隐藏,内边距,偏移量等)。最终汇聚成看到的展示样貌。

三、各类的职责

要看清各个部件是如何进行逻辑的组织及代码的编写,得分清各个类的职责是什么,负责哪一模块,然后才能更好地理通整个源码的思想。

  1. ~State模块主要处理状态:时间和不同状态的title
  2. ~Normal模块主要处理:箭头的方向,是否显示及loadingView的是否显示。
  3. ~Component基模块:主要暴露相应的接口,block供给子类进行组装及监听等的处理。
  4. ~Header 、~Footer模块:初始化的操作、设置frame的高度、scrollview的布局操作(inset, offset等)。

四、Header

下拉刷新刷新的方式有两种:一种是首次进入页面的时候,就主动触发代码刷新的操作,使其开始下拉刷新。另一种是手动下拉scrollview,进行的一系列UI操作及其给出的反馈。

主动调用代码进行的刷新操作

调用流程:

  • -[MJRefreshHeader beginRefreshing]

    1. setState:(refreshing)
    2. 展示刷新样式(增加滚动区域,更新offest值为header的高度)
    3. 执行block内部的刷新请求(executeRefreshingCallback)。
    4. 当3中的请求回调操作执行完之后,当外部主动调用- endRefreshing时进行还原操作。
  • -[MJRefreshHeader endRefreshing]

    1. setState:(idle)
    2. 恢复刷新样式,头部消失(更改inset为初始值)
    3. 如果endrefreshing中有回调时就执行,没有就结束整个流程。

与UI进行交互出现的刷新操作

主要有通过下拉或者上滑出现的UI变动及刷新的逻辑等。后半部分是一致的。主要分析前半部分与UI进行交互,然后进行更新头部的动画的过程。

以下拉刷新为例:(其中h为header的高度)

  1. 当下拉scrollview时,触发其delegate方法:scrollViewContentOffsetDidChange:方法。

  2. 当drag == true 时,state状态一直在变化;

    • 下拉到临界值:
      (state == idle && offsetY < -h)时 --> state = pulling。此时状态文字要发生变化,箭头也要进行翻转。根据各类的职责去相应的类中更改即可。

    • 上拉到临界值:
      state == pulling && offsetY >= -h时,恢复state = idle。同时恢复箭头,文字等的状态。

  3. 当drag == false && state == pulling,即在拉过临界值,并且松手了,那么此之后执行的逻辑,就是下拉刷新过程。


五、Footer

比Header包含的状态稍微多一点,但是和header的逻辑是类似的。
Footer分为Auto和Back两种状态

  • 联系:与header的比较,之前处理 ~Header的逻辑现在迁移到了AutoFooter和BackFooter。

  • 区别:~BackFooter的刷新是通过手动拖拽,并且在tableview的尾部最后进行展示的。而~AutoFooter的刷新操作,当上滑到最后一个cell时,将会自动进行刷新的操作。

对于footer通常不会去主动调用- beginRefresing该方法。他在设置好相应的初始位置后,就开始进行手动与UI
进行交互的上拉刷新操作:

~BackFooter:
  1. footer的位置在屏幕的下方(无论是contentsize.height是否大于screensize.height).
  2. 之后的操作就不再赘述,和下拉刷新的流程真的一模一样。(可以通过源码进行对照查看)
~AutoFooter:
  1. footer.y = contentsize.height + △ (其中△是一些inset信息).
  2. 之后的刷新也和下拉是一致的内容.

六、常见的错误❌:

  • 问题出现点:在上拉刷新的时候,根据有没有更多数据,来更改state。

  • 代码:

    if pullup {
        self.endFooterRefreshing(noMoreData: noMoreData)
    } 
    else {
         self.tableView.mj_header.endRefreshing()
    }

    private func endFooterRefreshing(noMoreData: Bool) {
        if noMoreData {
            self.tableView.mj_footer.endRefreshingWithNoMoreData()
        } else {
            self.tableView.mj_footer.resetNoMoreData()
        }
    }
  • 导致结果:当所有数据都加载完毕后,重新下拉刷新,然后上拉加载时,由于footer处于nomoredata状态,而无法加载其他页的数据。

  • 分析:
    从pullup代码来看,当上拉加载完所有数据后,将状态更新为了nomoredata状态。
    当重新在该页面进行下拉刷新时,数据只加载page == 1 的数据,但是此时footer.state == nomoredata。
    当要进行上拉加载page == 2 的数据时,由于state == nomoredata,在源码中就直接结束了整个刷新的操作.

  • 源码:
    scrollViewContentOffsetDidChange:

    ~BackFooter:

    // 如果已全部加载,仅设置pullingPercent,然后返回
    if (self.state == MJRefreshStateNoMoreData) {
        self.pullingPercent = pullingPercent;
        return;
    }
    

    ~AutoFooter:

      if (self.state != MJRefreshStateIdle 
          || !self.automaticallyRefresh 
          || self.mj_y == 0) 
        return;
    
  • 解决方案:

      if pullup {
          self.tableView.endFooterRefreshing(noMoreData: noMoreData)
      } else {
          self.tableView.endHeaderRefreshing(noMoreData: noMoreData, count: newcount)
      }
    
    // 给scrollview类进行扩展
    extension UIScrollView {
      
      /// 尾部停止刷新
      ///
      /// - Parameter noMoreData: 下次刷新是否有新的数据
      func endFooterRefreshing(noMoreData: Bool) {
          guard mj_footer != nil else { return }
          if noMoreData {
              mj_footer.endRefreshingWithNoMoreData()
          } else {
              footerEndRefreshing(action: nil)
          }
      }
      
      /// 头部停止刷新
      ///
      /// - Parameter noMoreData: 下次刷新是否有新的数据
      /// - count: 返回的结果个数
      func endHeaderRefreshing(noMoreData: Bool, count: Int) {
          guard mj_header != nil else { return }
          mj_header.endRefreshing()
          updateFooterState(noMoreData: noMoreData, count: count)
      }
      
      /// 根据首次刷新数据, 更新footer状态
      ///
      /// - Parameter noMoreData: 下次刷新是否有新的数据
      /// - count: 返回的结果个数
      private func updateFooterState(noMoreData: Bool, count: Int) {
          guard mj_footer != nil else { return }
          if noMoreData {
              mj_footer.endRefreshingWithNoMoreData()
              if count == 0 {
                  mj_footer.isHidden = true
              }
          } else {
              mj_footer.isHidden = false
              mj_footer.resetNoMoreData()
          }
      }
    }
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容

  • 本文转载自J_Knight 的MJRefresh源码解析 MJRefresh是李明杰的作品,到现在已经有9800多...
    Detective41阅读 652评论 0 1
  • 1.前言 MJRefresh 是日常 iOS 开发中使用频率比较高的一款下拉刷新/上拉加载更多的第三方控件,平时似...
    RiverSea阅读 1,367评论 0 10
  • 李明杰老师的代表作之一MJRefresh可以说是棒棒的,很多小伙伴都会在没有什么特殊要求的情况下使用这个框架,简单...
    sunxu_cocoa阅读 517评论 0 1
  • MJRefresh是流行的下拉刷新控件,前段时间为了修复一个BUG,读了它的源码,本文总结一下实现的原理 下拉刷新...
    晚安的你我阅读 425评论 0 0
  • 下拉刷新01-默认 self.tableView.header = [MJRefreshNormalHeader ...
    Lewis海阅读 39,470评论 12 14