[這是第一篇]
導(dǎo)語:UITableView是iOS項目中使用相關(guān)廣泛的UI組件腌零,本文主要從構(gòu)建界面方案的實現(xiàn)梯找、TableViewCell之間的通信、Native動態(tài)化頁面的野望(挖坑)三部分討論了如何使用TableView構(gòu)建比較常見的UI界面益涧。
一锈锤、概述
1、問題#####
在iOS的開發(fā)中闲询,有大量的UI工作久免,這些UI工作簡言之就是在畫“頁面”,“頁面”的復(fù)雜度有高有低扭弧,復(fù)雜度高的比如繪制直播畫面(聊天列表阎姥、點贊飛花、送禮物鸽捻、廣告UI等)呼巴,復(fù)雜度低的比如繪制一個關(guān)于頁面,一個logo和幾行文本就搞定了御蒲。
而我們在實際工作中遇到的UI工作大部分是中等復(fù)雜度的頁面衣赶,比如某某列表頁、某某詳情頁厚满,在繪制頁面的同時府瞄,還需要響應(yīng)頁面上的操作。
其實碘箍,很多頁面都是可以利用UITableView來構(gòu)建出來的遵馆。
2、優(yōu)勢#####
采用分而治之的辦法敲街,將一個整體頁面劃分成若干部分团搞,每一個部分就相當一類Cell,其實這么做是有好處的多艇,好處如下:
提高部分UI組件的復(fù)用性。尤其在同一個App中像吻,甚至一個App中的同一功能模塊中峻黍,有些UI是相同的,是可以復(fù)用的拨匆。
支持數(shù)據(jù)驅(qū)動UI的展示姆涩,在頁面數(shù)據(jù)變化的情況下,UI也能發(fā)生對應(yīng)的變化惭每。
在繪制同一個頁面時候骨饿,有利于多人合作亏栈,每個人可以負責(zé)一個頁面中不同的部分,且在開發(fā)過程中宏赘,不受其他人的工作進度影響绒北。
3、解決方案
封裝TableViewController察署,凡是想使用UITableView構(gòu)建頁面的Controller都需要繼承該類闷游。
支持UI組件響應(yīng)的的處理。
使用通知中心或其他方案來完成Cell之間的通信贴汪。
支持UITableView的上拉加載和下拉刷新脐往,一般列表頁面比較需要這兩個功能。
二扳埂、 構(gòu)建界面方案的實現(xiàn)####
1业簿、QSTableViewCell 和 QSTableViewCellModel
QSTableViewCell是對UITableViewCell的封裝,而QSTableViewCellModel是對QSTableViewCell上數(shù)據(jù)和UI響應(yīng)動作的描述阳懂。
-
QSTableViewCell定義了其子類需要實現(xiàn)的辦法辖源,如如數(shù)據(jù)更新布局,cellHeight處理希太,點擊cell的處理等主要方法克饶。
#pragma mark - QSTableViewCell @interface QSTableViewCell : UITableViewCell #pragma mark - 子類需重寫 - (void)layoutWithModel:(id)model; + (CGFloat)cellHeightWithModel:(id)model; - (void)onTapCellAction; @end
-
QSTableViewCellModel定義了對應(yīng)Cell類的類名厂榛、展示需要的數(shù)據(jù)恨锚, 以及響應(yīng)UI操作點擊的動作,我們將響應(yīng)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屬性保存對應(yīng)的Cell類的類名堕澄,可以利用反射來實現(xiàn)cell的創(chuàng)建邀跃。
2、實現(xiàn)UITableView代理方法
選擇在TableViewController中實現(xiàn)UITableView的UITableViewDelegate蛙紫、UITableViewDataSource的代理方法拍屑。在TableViewController中有頂一個dataSource數(shù)組,這個數(shù)組中存放的各個Cell對應(yīng)的CellModel坑傅,利用cellModel中的信息僵驰,完成Cell的繪制和響應(yīng)處理。以cell的創(chuàng)建唁毒、cell高度獲取為例蒜茴。
-
根據(jù)cellModel來實現(xiàn)heightForRowAtIndexPath代理
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ QSTableViewCellModel *cellModel = [self.dataSource objectAtIndex:indexPath.section]; return [self.cellFactory cellHeightForData:cellModel]; }
-
根據(jù)cellModel來完成cell的創(chuàng)建和更新
- (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:代理方法中實現(xiàn)了cell的創(chuàng)建、復(fù)用浆西、數(shù)據(jù)更新粉私,添加cell上的響應(yīng)動作處理。
-
QSTableViewCellFactory的職責(zé):負責(zé)cell高度的計算近零、創(chuàng)建和復(fù)用
//QSTableViewCellFactory的定義 @interface QSTableViewCellFactory : NSObject - (instancetype)initWithTableView:(UITableView *)tableView; - (CGFloat)cellHeightForData:(id)data; - (QSTableViewCell *)cellInstanceForData:(id)data; @end
說明:self.cellFactory就是QSTableViewCellFactory對象诺核,達到解耦的目的抄肖,使得Controller中代碼更簡潔。
3窖杀、上拉加載和下拉刷新
利用MJRefresh來實現(xiàn)TableView的上拉加載和下拉刷新漓摩。
-
設(shè)置上拉加載
- (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]]; }
-
設(shè)置下拉刷新
- (void)setupRefreshHeader{ //下拉刷新 @weakify(self); MJRefreshHeader *header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ [self refreshDataWithStyle:QSRefreshTableViewDataStylePull]; }]; self.tableView.mj_header = header; }
-
結(jié)束刷新
- (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]; } } }
總結(jié): 使用UITableView構(gòu)建頁面,Controller繼承自定義的TableViewController陈瘦,使用Cell來定義視圖幌甘,CellModel來定義視圖上的內(nèi)容和動作,然后將CellModel放入TableViewController的dataSource數(shù)組中痊项,即可锅风。
三、TableViewCell之間的通信####
在使用TableView組織頁面的時候鞍泉,有個非常值得考慮的問題皱埠,那就是TableViewCellCell之間的的通信。
1咖驮、概述#####
采用分而治之的思路边器,將一個大的視圖拆分成若干個TableViewCell。而在業(yè)務(wù)上托修,這幾個TableViewCell視圖是相互關(guān)聯(lián)的忘巧。如點擊某子視圖,另一個子視圖的數(shù)據(jù)展示發(fā)生變化睦刃。這就導(dǎo)致了需要選擇方案去實現(xiàn)部分TableViewCell之間的通信砚嘴。
早期方案是選用了通知中心;
通知中心不僅可以在不同的UITableViewCell之間傳遞數(shù)據(jù)的涩拙,還降低了代碼耦合际长。但是這也導(dǎo)致了TableViewCell中大量存在注冊觀察,移除觀察這樣的代碼(移除的工作還非常重要)兴泥。最關(guān)鍵的是工育,通知中心傳遞的參數(shù)不可以靈活地使用model對象。
后期方案是QSMessageCenter搓彻,這是從業(yè)務(wù)出發(fā)如绸,實現(xiàn)的通知中心的替換方案。
QSMessageCenter主要實現(xiàn)了不同對象之間的數(shù)據(jù)傳遞好唯;傳遞的參數(shù)可以是任何的對象,包括數(shù)據(jù)model竭沫,數(shù)組,字典等;注冊后不需要手動移除骑篙; 使用簡單和代碼耦合低。
2森书、通信方案的使用#####
通信方案使用了QSMessageCenter靶端,主要使用步驟如下:
-
在觀察者中注冊谎势,指定receiverKey
[self registerMessageReceiverWithKey:receiverKey];
-
發(fā)送消息,指定receiverKey杨名,當注冊方法和發(fā)送消息中的receiverKey相同脏榆,才可以把消息發(fā)送給觀察者
[self sendMessage:message messageId:messageId receiverKey:receiverKey];
-
在觀察者類中實現(xiàn)qsReceiveMessage:messageId:方法
- (void)qsReceiveMessage:(id)message messageId:(NSString *)msgId{ //可以根據(jù)msgId去做不同處理 }
說明:QSMessageCenter的具體詳情參考iOS實錄7:iOS通知中心的替換方案
四、Demo展示####
- 根據(jù)方案台谍,定義兩類Cell和CellModel,可以實現(xiàn)如下的列表頁须喂。
- 根據(jù)方案,復(fù)用列表頁的一類Cell和CellModel趁蕊,新增一類Cell和CellModel,可以實現(xiàn)如下的詳情頁坞生。
總結(jié):這樣的方案,為實現(xiàn)列表頁掷伙、詳情頁省去了大量的時間是己,開發(fā)者只需要集中精力做好Cell的繪制、CellModel的定義即可任柜。
五卒废、Native動態(tài)化頁面的野望(挖坑)
H5頁面有個很大的優(yōu)點,就是可以不用發(fā)版就可以更新頁面內(nèi)容宙地;如果可以利用TableView實現(xiàn)動態(tài)化的Native頁面摔认,豈不是很完美的事情。然后宅粥,現(xiàn)實的需求和業(yè)務(wù)比較復(fù)雜多變参袱,實現(xiàn)這樣的方案,投入和產(chǎn)出比是多少粹胯,能不能達到目標蓖柔,都不好判斷。但是針對某些特性的場景风纠,實現(xiàn)頁面的動態(tài)化數(shù)據(jù)更新還是可以的况鸣。
1、數(shù)據(jù)描述界面的各個組成#####
使用的是JSON格式數(shù)據(jù)竹观,由后臺返回镐捧,如下所示(部分JSON數(shù)據(jù)):
{
"body" :[{
"type": 0,
"content": "http://xxxxxxxxxxxxxxxxxxxxx.jpg",
"target":"appName://previewSingleImage"
},
{
"type": 1,
"content": "H5頁面有個很大的優(yōu)點,就是可以不用發(fā)版就可以更新頁面內(nèi)容臭增;如果可以利用TableView實現(xiàn)動態(tài)化的Native頁面懂酱,豈不是很完美的事情....",
"target":"appName://lookAllContent"
},
{
"type": 2,
"content": "http://xxxxxxxxxxxxxxxxx.mp4",
"target":"appName://playInFullScreen"
}
//其他略
]
//其他略
}
說明1:type定義的是內(nèi)容的類型,如0是圖片誊抛,1是文本列牺,2是視頻;content定義的是內(nèi)容拗窃;tagert定義的是點擊內(nèi)容的行為瞎领;tagert值的是App中自己定義的協(xié)議泌辫,如appName://previewSingleImage表明點擊圖片Cell預(yù)覽全圖,appName://playInFullScreen是點擊視頻Cell九默,全屏播放視頻震放。
2、動態(tài)化UI方案#####
有兩種方案可以選:
1)在Native實現(xiàn)盛裝不同數(shù)據(jù)類型的容器Cell驼修,和Cell的響應(yīng)處理行為殿遂。
說明:該方案沒有深入下去,因為TableView處理的視圖比較簡單乙各,而且還給后臺帶來比較大的負擔(dān)(本來后臺開發(fā)資源就緊張)墨礁,后期考慮使用CollectionView來實現(xiàn)特定場景下較復(fù)雜的動態(tài)化界面。
2)將數(shù)據(jù)和展示需求拼接成H5代碼觅丰,然后由WebView渲染出來饵溅,結(jié)合JS和WebViewJavascriptBridge處理Cell的響應(yīng)處理行為。
說明:該方案是目前采納的方法妇萄,一定程度上實現(xiàn)了頁面的動態(tài)化蜕企,比直接加載H5頁面快很多。但是也僅僅是適用于某些固定展示行為的模塊冠句,如文章詳情頁等轻掩。
3、 總結(jié)#####
目前的想法和設(shè)計都不完善懦底,適用范圍有限唇牧。
在簡書看到一篇博文,作者講了他使用實現(xiàn)動態(tài)化電商Native頁面的思路聚唐。感興趣可以去看看iOS頁面動態(tài)化丐重,怎么樣用JSON數(shù)據(jù)的原生頁面擺脫低效的H5頁面,來動態(tài)更新app頁面樣式
End
我是一個iOS開發(fā)程序猿杆查,至今(2017年7月)正式做iOS開發(fā)已有1年多點的光景扮惦。iOS實(踐)錄系列是自己的一點開發(fā)心得。這個系列是17年4月1日開始寫的亲桦,不僅是總結(jié)自己開發(fā)中經(jīng)驗崖蜜,也是對自己的鞭策。