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,可以实现如下的列表页。
- 根据方案,复用列表页的一类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日开始写的,不仅是总结自己开发中经验,也是对自己的鞭策。