[這是第一篇]
導語: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,可以實現如下的列表頁。
- 根據方案,復用列表頁的一類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、 總結#####
目前的想法和設計都不完善,適用范圍有限。
在簡書看到一篇博文,作者講了他使用實現動態化電商Native頁面的思路。感興趣可以去看看iOS頁面動態化,怎么樣用JSON數據的原生頁面擺脫低效的H5頁面,來動態更新app頁面樣式
End
我是一個iOS開發程序猿,至今(2017年7月)正式做iOS開發已有1年多點的光景。iOS實(踐)錄系列是自己的一點開發心得。這個系列是17年4月1日開始寫的,不僅是總結自己開發中經驗,也是對自己的鞭策。