iOS一個類搞定UIScrollView那些事

50480350 8年前發布 | 17K 次閱讀 IOS iOS開發 移動開發 UIScrollView

前言

UIScrollView可以說是我們在日常編程中使用頻率最多、擴展性最好的一個類,根據不同的需求和設計,我們都能玩出花來,當然有一些需求是大部分應用通用的,今天就聊一下以下需求,在一個category中統統搞定:

1.下拉刷新:支持下拉過程中GIF逐幀,loading時可自定義幀率

2.上拉更多:支持GIF,支持提前加載,滾動到最后能替換圖片作為提示

3.回到頂部:當滾動多屏之后,往回劃時右下角彈出回到頂部按鈕

4.自定義一個按鈕,在回到頂部按鈕上面,可自定事件,并且會根據回到頂部按鈕的出現或者消失上下移動,有動畫過渡

此類適用于任何繼承于scrollView的類

下拉刷新、上拉更多

在頭文件中我們聲明了2個bool的屬性,在沒有特殊圖片要求的情況下,我們只需要對這2個bool進行操作就可以了,分別是 useRefreshHeader和useLoadMoreFooter,由于是category,我們需要用 ASSOCIATION來為屬性添加set和get方法,并且在set方法中,我們為其創建一個默認的view,使用默認的文案和默認的gif圖片,也就是上圖中你們所看到的貓頭,當然也可以自定義gif,并且指定其播放的速度,為此,我提供了以下幾個接口方法:

//自定義gif下拉刷新
//傳入圖片名稱

  • (void)setRefreshProgressImageName:(NSString *)progressImageName //下滑時的圖片
                 LoadingImageName:(NSString *)loadingImageName    //加載時的圖片
                       showTitles:(NSArray *)titles               //顯示的title
            LoadingImageFrameRate:(NSInteger)frameRate;           //加載動畫的幀率
    
    //傳入圖片對象
  • (void)setRefreshProgressImage:(UIImage *)progressImage //下滑時的圖片
                 LoadingImage:(UIImage *)loadingImage    //加載時的圖片
                   showTitles:(NSArray *)titles      //顯示的title
        LoadingImageFrameRate:(NSInteger)frameRate;   //加載動畫的幀率
    
    //自定義gif上拉更多 //傳入圖片名稱
  • (void)setLoadMoreProgressImageName:(NSString *)progressImageName //跟手動畫
                 LoadingImageName:(NSString *)loadingImageName    //加載動畫
                          showTitles:(NSArray *)titles            //顯示的title
               LoadingImageFrameRate:(NSInteger)frameRate;        //加載動畫的幀率
    
    //傳入圖片對象
  • (void)setLoadMoreProgressImage:(UIImage *)progressImage //跟手動畫

                  LoadingImage:(UIImage *)loadingImage     //加載動畫
                        showTitles:(NSArray *)titles       //顯示的title
             LoadingImageFrameRate:(NSInteger)frameRate;   //加載動畫的幀率</code></pre> 
    

    更為關鍵的,我們需要用KVO監聽以下幾個屬性:

    1.contentOffset

    2.contentSize

    3.frame

    4.contentInset

    屬性監聽

    1.監聽contentOffset的目的

    由于我們使用了category,所以無法通過scrollView的delegate來獲取其滾動,和一般的下拉刷新一樣,我們在scrollView的頭部添加了一個View,所以必須監聽contentOffset才能做出行為判斷,上拉更多亦是如此,廢話不多說,直接上代碼:

    if([keyPath isEqualToString:@"contentOffset"])
      {        
          if (self.useRefreshHeader && self.refreshHeaderView && [self.refreshHeaderView isKindOfClass:[TMMuiPullView class]] && [self.refreshHeaderView respondsToSelector:@selector(scrollViewDidScroll:)])
          {
              [self.refreshHeaderView scrollViewDidScroll:[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]];
          }
    
          if (self.useLoadMoreFooter && self.loadMoreFooterView && [self.loadMoreFooterView isKindOfClass:[TMMuiPullView class]] && [self.loadMoreFooterView respondsToSelector:@selector(scrollViewDidScroll:)])
          {
              [self.loadMoreFooterView scrollViewDidScroll:[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]];
          }
      }

    我們通過contentOffset的變化模擬出了一個scrollViewDidScroll的方法,并且在refreshHeaderView和loadMoreFooterView中來監聽此方法,而這2個view都是TMMuiPullView,所以其實我只需要實現一次,我會在下文中來詳細談這個方法。

    2.監聽contentSize和frame的目的

    由于useRefreshHeader和useLoadMoreFooter聲明之后,無法避免需要改動scrollView的contentSize和frame的值,所以每當這2個值發生變化的時候,我們需要去調整這2個view的位置和布局

    3.監聽contentInset的目的

    ios 7之后,scrollView在一定條件下,系統會調整其contentInset,或者人為的調整了contentInset,為了能讓scrollView在各種動作之后依然處于正確的位置上,我們必須監聽這個值,并且儲存起來。

    TMMuiPullView 中的scrollViewDidScroll方法

    這個方法可謂是本類中最繁瑣的方法,任何一個動作都需要區分是刷新還是更多2個情況來討論

    TMMuiPullViewTypeRefresh

    1.當滾動的offset.y是大于0的時候,我們就直接return,因為之后不可能觸發下拉刷新的動作,也就沒有必要繼續往下走了;

    2.我們假設拉到觸發刷新動作的距離是100%的話,那么在未觸發前都會有對應的一個進度,通過這個進度我們去gif中獲取處于這 個進度的那一幀圖片,并且把他顯示到View上,從而達到跟手逐針播放的效果

    3.接下來就是狀態判斷了,在此,我們為其定制了5個狀態:

    typedef NS_ENUM(NSUInteger, TMMuiPullState)
    {
      TMMuiPullStateNone = 0,   //正常狀態
      TMMuiPullStateTriggering,
      TMMuiPullStateTriggered,
      TMMuiPullStateLoading,
      TMMuiPullStateCanFinish
    };

    每一個狀態都將是成為下一個狀態的條件,這讓我想到了“密室逃脫”

    第一個房間–TMMuiPullStateNone 這個房間里我們通過滾動到偏移量小于固定某個值的時候&手指依舊按著,那么就能拿到下一個門的鑰匙。

    第二個房間–TMMuiPullStateTriggering 這個房間拿到鑰匙的條件是:進度達到100%;手指依舊按著。

    第三個房間–TMMuiPullStateTriggered 我們房間的難度也是越來越苛刻的,這個房間需要滿足3個條件:1.沒有在loading,2.手指放開,3.放開手指的瞬間,進度是大于95%的。當滿足這些條件的時候,播放loading動畫,調用delegate方法,然后恭喜你,進入下一個房間

    第四個房間–TMMuiPullStateLoading 奇怪,這個房間竟然沒有任何提示,也不用做任何操作,怎么回事?需要進入下一個房間的鑰匙其實需要delegate塞進來,否則,你永遠沒辦法進入下一個房間,所以在用的時候,我們需要在delegate方法調用loading,loading完成之后調用一個方法把新的房間鑰匙送進來。

    第五個房間TMMuiPullStateCanFinish 這個最后一個房間,我們就等著進度恢復成初始狀態,那么就能順利的完成這一次密室逃脫,狀態置為TMMuiPullStateNone

    TMMuiPullViewTypeLoadMore

    1.當hasMorePage等于yes的時候,我們用的是loading圖片,而當hasMorePage等于no時,我們就用沒有更多的圖片。 2.其余部分其實和TMMuiPullViewTypeRefresh是同一個道理,只是關鍵性的值發生了改變這里就不在重復展開了。

    回到頂部

    1.當設置ShowBackTopButton為Yes時,我們會為scrollView添加一個contentOffset的監聽

    2.創建一個回到頂部的button,并且添加到與整個scrollView的superView上,并且在屏幕下方隱藏。

    3.contentOffset 偏移量大于2屏,且往回滾動時,回到頂部按鈕向上做動畫上升,否則動畫下落

    4.當點擊回到頂部按鈕時,scrollView就調用setContentOffset的方法,滾回頂部

    滾回頂部上方的自定義按鈕

    1.在滾回頂部上方可以添加一個按鈕,位于整個試圖的右下角,當滾回頂部按鈕出現時,它也會隨之上升,相反會滾回原來的位置。

    注意點

    在這個類中我們需要有2個注意點:

    1.KVO 不要多次添加,當多次添加KVO時就會有多個監聽者監聽同一個事件,所以我在每次添加監聽的時候都會try著刪除一次,記住,一定要寫try,否則會crash。

    2.runtime交換方法,因為我有許多指針一直持有內存,如果不把這個指針置為nil,也會導致crash,但是我們知道category是沒辦法重寫方法的,所以我們只能用method_exchangeImplementations的方法來做交換,這里我交換了2個地方一個是dealloc,另一個是didMoveToSuperview,因為我們有一些view是放在superView上面的。

    總結

    雖然本篇博客并沒有提及任何實現的具體代碼,而是提供一種思路,希望通過了解這些思路,能構建出一個屬于你自己的scrollView的category,如果真的有需要,我會代碼脫敏之后分享。

    via: http://www.cocoachina.com/swift/20160516/16285.html

 本文由用戶 50480350 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!