一. 概述
一般项目里面用到UITableView的概率还是灰常大的, 我的项目从一开始也用了. 大概的来说就是类似一个收件箱的功能, 推送来一条消息就加一个cell, 这是很简单的. 蓝鹅, 接下来经历了两次需求更改:
1. 增加'数据本地化'的功能;
2. 增加查看历史消息功能, 也就是下拉加载更多的消息.
第一个还好说, 就是用FMDB弄了个简单的数据库, 差不多就是获取数据的途径变成从数据库获取而已;
在增加了第一个需求之后, 叠加提出第二个需求, 看起来也很简单哦, 不就是加数据嘛. 但是, 当我开始做起来的时候才发现, 坑蛮多的.
这篇文章是基于我自己项目所写的, 可能在一些地方处理比较特殊并不具备"普世价值"; 但从开发者学习的角度来说, 我始终认为思路比代码重要. 所以这里我可能在思路方面比较啰嗦点, 直接要代码的慎重选择啊.
先说一下, 需求二里面主要遇到的坑有两个:
坑一 : 下拉加载的10条消息, 显示直接在第0-4条, 要看第5-9条的话又得反方向上滑
为了解决坑一, 将tableview和cell都旋转了180°, 这时候, 坑二来了
坑二 : 在初始cell数量较少的时候, 推送新消息过来增加cell的时候导致界面跳动
二. 项目过程
2.1需求分析
由于第一个需求太过简单, 所以就忽略啦. 这里直接讲第二个需求的.
刚刚上面讲的不是很清楚, 这里补充一下第二个需求的具体:
1.进入到界面的时候, 里面要先显示最多10条消息;
2.下拉可以加载历史数据(每次10条);
3.当有新消息推送过来的时候, 要添加在最底部, 并滚动到最底部;
4.三日内做完.
刚看到这个需求的时候, 我的第一个反应是这太TM简单了, too simple! 看老夫如何在三天时间以内用半天做完再用两天半假装没做(tou)完(lan).
2.2 坑一
蓝鹅, 忙活半天过后, 我发现自己too young too naive????. 这就是上面说的坑一, 图是这样的, 可以看到点击"消息+1"的时候还算正常, 但是下拉加载更多消息(10条)的时候, 都是停留在最顶部, 也就是上面坑一说的, 要看第5-9条的时候, 得反方向上滑
正常的.gif
为了解决坑一, 我尝试过调试NSIndexPath并且使用scrollToRowAtIndexPath:<#(nonnull nsindexpath=""> atScrollPosition:<#(uitableviewscrollposition)#> animated:<#(bool)#>]方法来各种折腾....再过了半天之后????放弃了, 下班, 对, 下班!!!谁说程序猿都得加班的.
2.3 坑二
昨晚想了一(yi)下(晚)和简单的实践(说好的不加班呢, 变相加班????!), 发现在uitableview的底部使用inset...和scroll...方法拼接数据很完美, 脑洞大开," 独创"(后来发现网上也有)ios大法之乾坤大挪移----将整个tableview旋转180°!!!!! 当然cell也要旋转180°.
旋转的代码分别是:
CGAffineTransform transform =CGAffineTransformMakeRotation(M_PI); [tbV setTransform:transform];
cell的旋转:
CGAffineTransform transform =CGAffineTransformMakeRotation(M_PI); [self.contentView setTransform:transform];
当然, 使用mjrefresh的话, 就得用原来使用的mj_header换成mj_footer了(关于mjrefresh的使用).
弄好之后是这样的, cell直接在最底部:
倒过来一.gif
这明显是不行的, 为了使得cell数量比较少的时候可以顶置(实际是底置, 因为tableview旋转了180°了), 于是想到了根据tableView.contentSize.height(关于 tableView.contentSize你想了解的)来设置tableView.contentInset, 代码是这样的
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{ if (tableView.contentSize.height < tableView.frame.size.height) { tableView.contentInset = UIEdgeInsetsMake(tableView.frame.size.height - tableView.contentSize.height - RSRealValue(120), 0, 0, 0); } }
运行出来一看, 哇, 完美解决!!!! 蓝鹅, 高兴地太早了. 这时候下拉加载时没问题的, 但新加一条消息的时候, 新的cell会把原来的cell顶上去, 而不是拼接在最下面, 如图:
倒过来contentInset.gif
经过一番思考, 本人大致的认为, 在新加入cell的时候, 原来的tableView.contentSize还是没变, 也就是说tableView.contentSize的布局其实还是只到了上一个cell的底部, 并以此往复, 所以出现了上图里面加了3条新消息再下滑的时候才出现.
这时候, 又想到了一个方法, 可以尝试监听tableView.contentSize还动态修改tableView.contentSize.....&%$#@!@#$%&%$过程省略几百字, 直接说结果吧, 然并卵. 而且, 在ios的UITableView里面, 并不是很推荐修改tableView.contentSize, 因为这个是苹果自身维持动态平衡的, 我们强心修改的话会影响其平衡, 而且在控制台会出现警报. 所以很不推荐.
就这样又折腾了半天.....下午整个人都在想这个问题. 终于在吃了一颗糖果之后, 想到一个方法:
为啥不通过记录cell的总高度来代替tableView.contentSize, 然后设置一个透明的tableheaderview来代替设置tableView.contentInset快被自己的机智感动了.
这样既可以绕过上面说的问题了. 说干就干.
需要在cell的model文件里面增加一个记录本cell高度的属性:
// 计算出来的cell的高度 @property (nonatomic, assign) CGFloat cellHeight;
同时, 也得在控制器文件里面增加一个记录所有cell总高度的属性:
// cell的叠加高度 @property (nonatomic, assign) CGFloat cellsTotalHeight;
并且写了它的懒加载方法, 这样每次在获取总高度的时候都能保证是最新的
- (CGFloat)cellsTotalHeight{ CGFloat heightAll = 0; for (int i = 0; i < self.dataMulArr.count; i ++) { OneModel *model = self.dataMulArr[i]; heightAll = heightAll + model.cellHeight; } _cellsTotalHeight = heightAll; NSLog(@"cell总高度: %f",heightAll); return heightAll; }
同时, 返回头部视图和高度是这样de(为了方便调试, 先把头部视图设置成有个半透明色)
#pragma mark- 返回头部视图 - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{ UIView *headV = [[UIView alloc] init]; // headV.backgroundColor = [UIColor clearColor]; headV.backgroundColor = RSColorFromRGBA(0x00FF00, 0.5); return headV; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ NSLog(@"头部视图高度中cell: %f", self.cellsTotalHeight); CGFloat headHeight = 0; if (self.cellsTotalHeight < self.tbV.frame.size.height) { headHeight = self.tbV.frame.size.height - self.cellsTotalHeight - RSRealValue(240); } return headHeight; }
跑出来是这样的:
倒过来加header.gif
这时候再把tableheaderview的背景色改成透明的, 就成了这样了:
倒过来加header透明.gif
果然完美解决了, 这时候其实只剩一天时间假装没完(偷)成(lan)了.
不过还是要注意一下, 看图倒过来加header.gif将tableheaderview改成透明之后, 新增cell还是会被tableheaderview挡住, 所以最后一个cell是点击不到的. 因为我项目里面的cell不需要点击, 所以这个问题我可以忽略; 如果小伙伴项目里面的cell需要点击的话, 就要将tableheaderview高度进一步减去新增cell的高度哦. 这里我就不做具体代码了, 小伙伴们自己探索一下把.
三. 小总结
总结起来, 这里面几个关键点是:
1.旋转180°
2.不使用reloaddata方法, 而是使用inser....和scroll...两个方法
3.通过增加tableheaderview来实现倒转tableview之后cell的顶置, 并且动态的改变tableheaderview的高度.
这个虽然只是项目的一小部分, 但每次解决问题之后都能获得成就感, 这就是码农单纯的快乐吧.