与tableview相关的几个小问题总结
背景
最近在做辞书项目的检索模块,整体上分为一级,二级,三级,四级,五级检索。根据策划案的需求,做一个通用的检索模块,效果图如下:
2017-06-13 11_53_44.gif其中涉及到多个tablview或collectionview的联动使用的问题。并且在做的过程中也发现了几个与tableview性能和自定义cell中的lable字体点击态颜色或UIImageView点击态背景色不会正常显示的问题,特意总结一下,希望对大家有一些帮助。
正文
问题一
Tablview或Collectionview的多级联动使用
- 分析
实现联动主要有几个关键的触发事件的时机 :
1.用户点击的时候 2.用户拖拽滑动的时候
我们要做的就是在这几个时机去让对应的目标tableview滚动到指定的位置或显示相应的数据。
这里最主要的就是使用系统给我们提供的tableview滚动相应位置的方法:
- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated;
这个方法大家肯定都很熟悉,这里主要介绍一下使用这个方法应该注意的地方:
1.调用这个方法的前提是要保证你要滚动的位置已经有数据,
并且滚动的到row 不要超过这个区的最大row个数,否则会引起崩溃提示一下错误:
2017-05-31 10:49:34:040 cmp-dictionary1494317771689[2913] 【请不要直接使用NSLog输出日志,请使用MUPLog的日志等级方法进行输出】UMLOG: error: session_id=F0D2312F1C320B13389C7D8F8559108A, context=-[UITableView _contentOffsetForScrollingToRowAtIndexPath:atScrollPosition:]: row (0) beyond bounds (0) for section (1).
(null)
2.先来看几张效果图:
图-2.jpeg 图-3.jpeg如图-2所示,B区列表数据一开始是显示第0个区第一个数据,点击A区列表中的cell,B区列表切换数据后会显示第0个区第一个数据,如果B区列表如图-3所示一开始不是显示第0个区第一个数据 ,点击A区列表的cell,B区列表切换数据后不会显示第0个区第一个数据。
这个现象其实就是刷新数据后我们没有去让tabelview去滚动到第一个区的第一个数据引起的,当然可以继续调用上面的scrollToItemAtIndexPath 这个方法就可以解决,但是这里我想说的是调用scrollToItemAtIndexPath如果数据没处理好是很容易出现问题1中的崩溃的问题,为了最大限度的避免程序中崩溃的现象出现,我建议大家这里可以通过设置
tableveiw的setContentOffset 为 0 来让tablview 滚动到顶部。具体代码:
[self.contentTable setContentOffset:CGPointMake(0, 0)];
3.用户拖拽滑动的时候 需要注意的几个点
首先我们要实现下scrollerview的代理方法记录当前tablview的滑动方向,参考代码:
// 标记一下RightTableView的滚动方向,是向上还是向下
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
static CGFloat lastOffsetY = 0;
if (self.indexTable == scrollView || self.contentTable == scrollView ){
_isScrollDown = lastOffsetY < scrollView.contentOffset.y;
lastOffsetY = scrollView.contentOffset.y;
}
}
然后我们主要利用tableview的区头将要显示和隐藏的代理方法来做相应的滚动处理和加载新的数据,特别是在这个地方去加载新的数据是比较有意义的,这样就可以减少一开始就去加载大量的数据,而是需要的时候在加载这样会提高相应的性能。
参考代码:
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section{
if (self.model.level != DictSearchLevelThree) {
if((self.contentTable == tableView ) && !_isScrollDown && self.contentTable.dragging ){
if (section < self.headCollecDataArray.count) {
//在这里可以去滚动的相应地区的某个位置或加载将要显示的区的数据
[self selectItemAtIndexPath:section];
}
}
}
}
- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section{
if (self.model.level != DictSearchLevelThree) {
if((self.contentTable == tableView ) && _isScrollDown && self.contentTable.dragging ){
if (section < self.headCollecDataArray.count - 1) {
//在这里可以去滚动的相应地区的某个位置或加载将要显示的区的数据
[self selectItemAtIndexPath:section+1];
}
}
}
}
问题二 因一次疏忽引起的tableview加载缓慢的性能问题。
按照需求做一个如下图所示的简单的点击tableview区头展开这个区的数据,再点击就关闭这个区不显示数据。
图1.png写的时候直接粘贴了一段代码过去,直接在此基础修改开发,也没仔细区推敲tableview的细节,很快效果就出来了,但是测试的时候发现如果数据少的话加载速度还可以,但是如果数据很多的话就会发现第一次进入页面加载很慢,代码都一样只是数据多少不一样什么原因呢?于是就在代码中记录下每个阶段的系统的时间,通过对比每个阶段的耗时,首先排除了不是sdk加载数据慢的原因引起的,奇怪的是在测试的过程还发现不同的屏幕加载的耗时还不一样,什么原因呢?继续分析代码打断点排查,最后才发现这页面总共有24个区,默认只展开第一个区,但是每次进来tablview都会去刷新每个区的数据,所以就导致加载很慢,在仔细分析代码才发现是因为 前面粘贴复用的那段代码的处理方法是下面这样:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 10;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
testModel *model = self.listArray[indexPath.section];
//model.opened 当前区是否展开
if (model.opened) {
return 50;
}
return 0;
}
而正确的处理方法应该是这样:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
testModel *model = self.listArray[section];
//model.opened 当前区是否展开
if (model.opened) {
return 10;
}
return 0;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 50;
}
这两段代码的差异就是设置当前区关闭和展开的方式不同,虽然最终的效果是一样的但是这里的最终的性能是不一样的,为什么呢?原因其实很简单:我们都知道UITableView 继承自 UIScrollView ,它也有contentsize但是 我们使用的时候并没有去设置过它的contentsize,原因是在布局视图的时候,UITableView会先去执行numberOfRowsInSection 和 numberOfSection 这两个代理方法根据返回有几个区每个区有几个cell 计算出它contensize 然后才会去绘制每个cell上的控件,而如果我们某个区
返回的cell个数为0,它就不会去绘制刷新这个区的cell,上面的加载缓慢的原因就是这个。错误的写法会使得tablview 去把所有的区的cell都加载绘制不管你是否展开,只是代码中把没有展开的区的cell高度设为了0,所以才会导致最终的显示效果是一样的。至于为什么不同大小的屏幕加载速度也会不一样 是因为不同屏幕UITableView显示的区的个数不一样,所以就导致了加载速度不一样了。
- 反思:
其实这是是一个很小的点,也并不难。但是如果不怎么去注意的话很容易被忽略,而导致后面的性能问题。而我们通过问题去排查原因是很难的要过滤掉许多干扰条件 才可以得出最后的结果,但是如果我们从源头上避免这些问题的出现,就会给后面的维护优化带来很多便利。
问题三 自定义cell中点击cell时,设置lable字体的选中颜色和让UIImageView隐藏或改变背景图不会正常显示。
自定义cell然后再点击cell的时候让其中的文字或图片 显示为选中的状态,这是我们非常常见的一个需求。
通常我们的做法会去 重写cell的 setSelected 方法 像下面的代码一样,
- (void)setSelected:(BOOL)selected animated:(BOOL)animated{
[super setSelected:selected animated:animated];
//在这里根据selected写相应的逻辑
}
或者我们也可以使用UILable的highlightedTextColor ,UIImageView的highlightedImage来实现。但是如果你在点击cell后在去reloadTableview的话,就会出现上面方法中设置的效果都会无效,或者是闪一下就消失了。这是什么原因呢?当时我也很费解,通过分析和查找发现,因为我们让Tableview relaoddata 的话,Tableview是会把之前的cell都释放掉 然后在去创建新的cell 去加载出来,所以我们设置的属性也就都无效了。
- 解决办法:
1.我们可以在自定义的cell中重写 - (void)layoutSubviews 方法在这里通过记录的当前cell被点击的标志去设置相应的状态。
2.记录当前选中的是哪个区的哪个cell 然后在Tableview 的 cellForRowAtIndexPath 方法中这里根据
当前选中的是哪个区的哪个cell去改变相应的cell的状态。
当然如果你不需要让Tableview去执行relaoddata 的方法,上面两种方法都是可以的。
总结
上面的问题都是一些琐碎的小问题,在这里拿出来其实也是起一个抛砖引玉的作用,想告诉大家我们应该从一些小的问题出发,在写代码的过程中应从源头开始避免这些问题的发生,不然可能因为我们的疏忽就会导致后面要拿出很多的时间和精力去解决一个性能优化或者界面优化的问题,如果我们平时多积累和注意这些问题的话,那么我们就会减少很多不必要的麻烦。
最后给大家推荐个不错的公众号 "说神码",或者大家可以扫描下面的二维码关注
qrcode_for_gh_3b0177133bdb_258.jpg