iOS實錄1:UITableView構建UI界面

[這是第一篇]

導語:UITableView是iOS項目中使用相關廣泛的UI組件,本文主要從構建界面方案的實現TableViewCell之間的通信Native動態化頁面的野望(挖坑)三部分討論了如何使用TableView構建比較常見的UI界面。

一、概述

1、問題#####
  • 在iOS的開發中,有大量的UI工作,這些UI工作簡言之就是在畫“頁面”,“頁面”的復雜度有高有低,復雜度高的比如繪制直播畫面(聊天列表、點贊飛花、送禮物、廣告UI等),復雜度低的比如繪制一個關于頁面,一個logo和幾行文本就搞定了。

  • 而我們在實際工作中遇到的UI工作大部分是中等復雜度的頁面,比如某某列表頁、某某詳情頁,在繪制頁面的同時,還需要響應頁面上的操作。

  • 其實,很多頁面都是可以利用UITableView來構建出來的。

2、優勢#####

采用分而治之的辦法,將一個整體頁面劃分成若干部分,每一個部分就相當一類Cell,其實這么做是有好處的,好處如下:

  • 提高部分UI組件的復用性。尤其在同一個App中,甚至一個App中的同一功能模塊中,有些UI是相同的,是可以復用的。

  • 支持數據驅動UI的展示,在頁面數據變化的情況下,UI也能發生對應的變化。

  • 在繪制同一個頁面時候,有利于多人合作,每個人可以負責一個頁面中不同的部分,且在開發過程中,不受其他人的工作進度影響。

3、解決方案
  • 封裝TableViewController,凡是想使用UITableView構建頁面的Controller都需要繼承該類。

  • 支持UI組件響應的的處理。

  • 使用通知中心或其他方案來完成Cell之間的通信。

  • 支持UITableView的上拉加載和下拉刷新,一般列表頁面比較需要這兩個功能。

二、 構建界面方案的實現####

1、QSTableViewCell 和 QSTableViewCellModel
  • QSTableViewCell是對UITableViewCell的封裝,而QSTableViewCellModel是對QSTableViewCell上數據和UI響應動作的描述。

  • QSTableViewCell定義了其子類需要實現的辦法,如如數據更新布局,cellHeight處理,點擊cell的處理等主要方法。

     #pragma mark - QSTableViewCell
    @interface QSTableViewCell : UITableViewCell
    
    #pragma mark - 子類需重寫
    - (void)layoutWithModel:(id)model;
    
    + (CGFloat)cellHeightWithModel:(id)model;
    
    - (void)onTapCellAction;
    
    @end
    
  • QSTableViewCellModel定義了對應Cell類的類名、展示需要的數據, 以及響應UI操作點擊的動作,我們將響應UI操作的動作封裝在Block中。

    #pragma mark - QSTableViewCellModel
    @interface QSTableViewCellModel : NSObject
    
    @property (nonatomic,copy)NSString  *cellClassName;
    @property (nonatomic,copy)QSTableViewCellActionBlock tapCellBlock;
    @property (nonatomic,assign)CGFloat cellHeight;
    @property (nonatomic,strong)id userInfo;
    
    @end
    

說明:cellClassName屬性保存對應的Cell類的類名,可以利用反射來實現cell的創建。

2、實現UITableView代理方法

選擇在TableViewController中實現UITableView的UITableViewDelegate、UITableViewDataSource的代理方法。在TableViewController中有頂一個dataSource數組,這個數組中存放的各個Cell對應的CellModel,利用cellModel中的信息,完成Cell的繪制和響應處理。以cell的創建、cell高度獲取為例。

  • 根據cellModel來實現heightForRowAtIndexPath代理

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    
       QSTableViewCellModel *cellModel = [self.dataSource objectAtIndex:indexPath.section];
       return [self.cellFactory cellHeightForData:cellModel];
    }
    
  • 根據cellModel來完成cell的創建和更新

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
         QSTableViewCellModel *cellModel = [self.dataSource objectAtIndex:indexPath.section];
         QSTableViewCell *cell = [self.cellFactory cellInstanceForData:cellModel];
         [cell layoutWithModel:cellModel];
         return cell;
      }
    

    說明:在cellForRowAtIndexPath:代理方法中實現了cell的創建、復用、數據更新,添加cell上的響應動作處理。

  • QSTableViewCellFactory的職責:負責cell高度的計算、創建和復用

    //QSTableViewCellFactory的定義
    @interface QSTableViewCellFactory : NSObject
    
    - (instancetype)initWithTableView:(UITableView *)tableView;
    
    - (CGFloat)cellHeightForData:(id)data;
    
    - (QSTableViewCell *)cellInstanceForData:(id)data;
    
    @end
    

說明:self.cellFactory就是QSTableViewCellFactory對象,達到解耦的目的,使得Controller中代碼更簡潔。

3、上拉加載和下拉刷新

利用MJRefresh來實現TableView的上拉加載和下拉刷新。

  • 設置上拉加載

    - (void)setupRefreshFooter{
    
         //上拉加載更多
        @weakify(self);
         MJRefreshAutoNormalFooter *footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
              @strongify(self);
                  [self refreshDataWithStyle:QSRefreshTableViewDataStyleLoadMore];
          }];
    
          [footer setTitle:@"已顯示全部"forState:MJRefreshStateNoMoreData];
    
          footer.refreshingTitleHidden = YES;
          self.tableView.mj_footer = footer;
          [footer.stateLabel setTextColor:[UIColor lightGrayColor]];
      }
    
  • 設置下拉刷新

      - (void)setupRefreshHeader{
    
          //下拉刷新
          @weakify(self);
          MJRefreshHeader *header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
             [self refreshDataWithStyle:QSRefreshTableViewDataStylePull];
          }];
          self.tableView.mj_header = header;
      }
    
  • 結束刷新

    - (void)endRefreshingWithMoreData:(BOOL)hasMoreData{
    
        if (self.needPullRefresh) {
            [self.tableView.mj_header endRefreshing];
        }
    
        if (self.needPullToLoadMore) {
            if (hasMoreData) {
                [self.tableView.mj_footer endRefreshing];
            }else{
                [self.tableView.mj_footer endRefreshingWithNoMoreData];
            }
        }
    }
    

總結: 使用UITableView構建頁面,Controller繼承自定義的TableViewController,使用Cell來定義視圖,CellModel來定義視圖上的內容和動作,然后將CellModel放入TableViewController的dataSource數組中,即可。

三、TableViewCell之間的通信####

在使用TableView組織頁面的時候,有個非常值得考慮的問題,那就是TableViewCellCell之間的的通信。

1、概述#####
  • 采用分而治之的思路,將一個大的視圖拆分成若干個TableViewCell。而在業務上,這幾個TableViewCell視圖是相互關聯的。如點擊某子視圖,另一個子視圖的數據展示發生變化。這就導致了需要選擇方案去實現部分TableViewCell之間的通信。

  • 早期方案是選用了通知中心

  • 通知中心不僅可以在不同的UITableViewCell之間傳遞數據的,還降低了代碼耦合。但是這也導致了TableViewCell中大量存在注冊觀察,移除觀察這樣的代碼(移除的工作還非常重要)。最關鍵的是,通知中心傳遞的參數不可以靈活地使用model對象。

  • 后期方案是QSMessageCenter,這是從業務出發,實現的通知中心的替換方案。

  • QSMessageCenter主要實現了不同對象之間的數據傳遞;傳遞的參數可以是任何的對象,包括數據model,數組,字典等;注冊后不需要手動移除; 使用簡單和代碼耦合低。

2、通信方案的使用#####

通信方案使用了QSMessageCenter,主要使用步驟如下:

  • 在觀察者中注冊,指定receiverKey

    [self registerMessageReceiverWithKey:receiverKey];
    
  • 發送消息,指定receiverKey,當注冊方法和發送消息中的receiverKey相同,才可以把消息發送給觀察者

    [self sendMessage:message messageId:messageId receiverKey:receiverKey];
    
  • 在觀察者類中實現qsReceiveMessage:messageId:方法

    - (void)qsReceiveMessage:(id)message messageId:(NSString *)msgId{
        //可以根據msgId去做不同處理
    }
    

說明:QSMessageCenter的具體詳情參考iOS實錄7:iOS通知中心的替換方案

四、Demo展示####

  • 根據方案,定義兩類Cell和CellModel,可以實現如下的列表頁。
列表頁效果圖.png
  • 根據方案,復用列表頁的一類Cell和CellModel,新增一類Cell和CellModel,可以實現如下的詳情頁。
詳情頁效果圖

總結:這樣的方案,為實現列表頁、詳情頁省去了大量的時間,開發者只需要集中精力做好Cell的繪制、CellModel的定義即可。

五、Native動態化頁面的野望(挖坑)

H5頁面有個很大的優點,就是可以不用發版就可以更新頁面內容;如果可以利用TableView實現動態化的Native頁面,豈不是很完美的事情。然后,現實的需求和業務比較復雜多變,實現這樣的方案,投入和產出比是多少,能不能達到目標,都不好判斷。但是針對某些特性的場景,實現頁面的動態化數據更新還是可以的。

1、數據描述界面的各個組成#####

使用的是JSON格式數據,由后臺返回,如下所示(部分JSON數據):

{
   "body" :[{
      "type": 0, 
       "content": "http://xxxxxxxxxxxxxxxxxxxxx.jpg",
       "target":"appName://previewSingleImage"
    },
    {
        "type": 1, 
        "content": "H5頁面有個很大的優點,就是可以不用發版就可以更新頁面內容;如果可以利用TableView實現動態化的Native頁面,豈不是很完美的事情....",
        "target":"appName://lookAllContent"
    },
    {
        "type": 2, 
        "content": "http://xxxxxxxxxxxxxxxxx.mp4",
        "target":"appName://playInFullScreen"
    }
    //其他略
  ]
//其他略
}

說明1:type定義的是內容的類型,如0是圖片,1是文本,2是視頻;content定義的是內容;tagert定義的是點擊內容的行為;tagert值的是App中自己定義的協議,如appName://previewSingleImage表明點擊圖片Cell預覽全圖,appName://playInFullScreen是點擊視頻Cell,全屏播放視頻。

2、動態化UI方案#####

有兩種方案可以選:

1)在Native實現盛裝不同數據類型的容器Cell,和Cell的響應處理行為。

說明:該方案沒有深入下去,因為TableView處理的視圖比較簡單,而且還給后臺帶來比較大的負擔(本來后臺開發資源就緊張),后期考慮使用CollectionView來實現特定場景下較復雜的動態化界面。

2)將數據和展示需求拼接成H5代碼,然后由WebView渲染出來,結合JS和WebViewJavascriptBridge處理Cell的響應處理行為。

說明:該方案是目前采納的方法,一定程度上實現了頁面的動態化,比直接加載H5頁面快很多。但是也僅僅是適用于某些固定展示行為的模塊,如文章詳情頁等。

3、 總結#####

End

  • 源碼QSUseTableViewDemo

  • 我是一個iOS開發程序猿,至今(2017年7月)正式做iOS開發已有1年多點的光景。iOS實(踐)錄系列是自己的一點開發心得。這個系列是17年4月1日開始寫的,不僅是總結自己開發中經驗,也是對自己的鞭策。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 概述在iOS開發中UITableView可以說是使用最廣泛的控件,我們平時使用的軟件中到處都可以看到它的影子,類似...
    liudhkk閱讀 9,085評論 3 38
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,200評論 30 471
  • 前言 由于最近兩個多月,筆者正和小伙伴們忙于對公司新項目的開發,筆者主要負責項目整體架構的搭建以及功能模塊的分工。...
    CoderMikeHe閱讀 27,087評論 74 271
  • 媽媽老了,耳朵有些背了,記憶力也不大好,常常叫錯我們兄弟倆的名字。解決辦法是要常常讓她喜悅地參與活動,用笑話刺激大...
    chchfo閱讀 337評論 3 1
  • 我像每個人都一段第一次獨自走夜路的歷程,我也不例外,不同尋常,它像一次刻骨銘心的修行讓我明白了很多。 那是...
    灰叔漫畫閱讀 977評論 6 10