一. 关于UITableView
1. UITableViewStyle
作为iOS开发者UITableView可能是最为常用的一个控件,我们都知道在创建UITableView的时候有两种样式可供选择UITableViewStylePlain、UITableViewStyleGrouped。
关于这两种样式到底有什么区别,该如何选择。说实话,在相当长的一段时间里我是稀里糊涂的。下面我来分享一下经验,仅供参考,若有偏差望请指正!
① UITableViewStylePlain
- 如果有sectionHeader,区头会出现悬浮吸附的效果。
- 如果没有sectionHeader、sectionFooter,cell会铺满整个table。
- UITableViewStylePlain的tableView可以有一个section索引,作为一个bar在table的右边(例如A ~ Z)。你可以点击一个特定的标签,跳转到目标section,例如iPhone的通讯录。
补充:
UITableViewStylePlain样式可以使用系统的索引条,使用方法如下:
//返回索引的数组
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{
//索引文字颜色
tableView.sectionIndexColor = [UIColor snbcl_colorWithHexString:@"55607C"];
//索引条背景颜色
tableView.sectionIndexBackgroundColor = [UIColor clearColor];
//返回索引数组
return [NSArray arrayWithObjects:@"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H",@"I",@"J",@"K",@"L",@"M",@"N",@"O",@"P",@"Q",@"R",@"S",@"T",@"U",@"V",@"W",@"X",@"Y",@"Z",@"#", nil];
}
//点击了哪个索引
-(NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index{
//点击系统的某个索引,就会回调这个方法,title为索引的标题,index为点击了哪个索引
//我们需要返回一个值,用来告诉系统,tableView滚动到哪个索引
return 0;
}
② UITableViewStyleGrouped
- 如果有sectionHeader、sectionFooter,区头区尾会和cell一样正常滚动,没有悬浮吸附的效果。
- 如果没有人为设置sectionHeader、sectionFooter,会有个默认的sectionHeader、sectionFooter效果。
- 如果不想要这个默认的sectionHeader、sectionFooter效果,可以设置他们的高度为很小值,不能为0,为0系统会使用默认值,设置为最小值之后,cell会铺满整个table。
- UITableViewStyleGrouped的tableView不能有一个(右边的)索引,比如iPhone的设置界面。
共同点:
两种样式都可以设置tableHeaderView、tableFooterView,并且都没有悬浮吸附的效果。
注意点:
- tableview的sectionFooterHeight、sectionFooterHeight属性只在UITableViewStyleGrouped类型,并且未实现代理方法tableView:heightForHeader/FooterInSection: 时有效。
- 所以设置sectionHeader、sectionFooter的高度以及自定义sectionHeader、sectionFooter的时候最好使用如下代理方法,这样就不用考虑这么多了。
//设置sectionHeader高度
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 0.00001;
}
//设置sectionFooter高度
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 0.00001;
}
总结:
- 如果有sectionHeader吸附悬浮的需求,或者你不需要sectionHeader、sectionFooter,那么建议使用UITableViewStylePlain。
- 如果你需要使用sectionHeader、sectionFooter,并且没吸附效果的要求,那么建议使用UITableViewStyleGrouped。
- 很容易理解:Plain样式有悬浮效果,所以可以设置索引条,Grouped样式本来就是分组的意思,所以会有系统自动生成的sectionHeader、sectionFooter
2. UITableViewCellStyle
系统的UITableViewCell有四种样式,如果cell不是太复杂我们可以使用系统的cell,先看代码:
- (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0,StatusAndNavBarHight,ScreenWidth,ViewSafeHeight) style:UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
// 设置cell分隔线
_tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
// 默认情况下不显示cell的地方也有分割线,解决办法如下:
_tableView.tableFooterView = [UIView new];
}
return _tableView;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellId = @"cellid";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellId];
}
cell.imageView.image = [UIImage imageNamed:@"car"];
cell.textLabel.text = @"我是测试标题";
cell.detailTextLabel.text = @"我是测试副标题";
//设置附件样式,一般使用右箭头样式
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// 设置cell选择样式 默认选中灰色
cell.selectionStyle = UITableViewCellSelectionStyleDefault;
// 设置分隔线的位置 默认是{0, 15, 0, 0}即左边有15的距离没横线
// 如果设置为UIEdgeInsetsZero,则整个宽度都有横线
// cell.separatorInset = UIEdgeInsetsZero;
return cell;
}
系统的UITableViewCell有四个子控件,分别是imageView、textLabel、detailTextLabel、accessoryType。
对于每一种样式,并不是所有的子控件都可以显示,具体效果如下:
① UITableViewCellStyleDefault
如果是Default样式,最多只能显示imageView、textLabel、accessoryType这三个控件,如果不设置imageView,则textLabel会往左靠,如果设置accessoryType为UITableViewCellAccessoryNone,则向右的箭头不显示。
② UITableViewCellStyleSubtitle
同理,如果不设置imageView,则右边的控件会左移,如果右边的控件只设置一个那么这个控件会居中显示。
③ UITableViewCellStyleValue1
detailTextLabel会一直固定在右侧,如果不设置imageView,textLabel会左移。
④ UITableViewCellStyleValue2
蓝色的是textLabel,黑色的是detailTextLabel,如果只设置一个,另外一个会往左移。
补充:
- 默认点击cell之后一直有选中效果,如果想点击一下再取消选中效果,可写如下代码:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- 当我们设置accessoryType = UITableViewCellAccessoryDisclosureIndicator;会显示向右的箭头,当我们设置accessoryView之后,优先显示accessoryView不显示向右的箭头,比如设置accessoryView为UISwitch,如下:
总结:
如果我们不需要detailTextLabel,直接使用默认的样式就好,如果需要detailTextLabel,可以使用Value1样式。
3. UITableView的简单使用
//创建tableView
- (UITableView *)mineTableView
{
if (!_mineTableView) {
_mineTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, StatusBarHeight + NavigationBarHeight, ScreenWidth, ViewSafeHeight) style:UITableViewStyleGrouped];
_mineTableView.showsVerticalScrollIndicator = NO;
_mineTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_mineTableView.delegate = self;
_mineTableView.dataSource = self;
_mineTableView.backgroundColor = [UIColor colorWithHex:0xf3f3f3];
_mineTableView.accessibilityIdentifier = @"XUMineTableView";
_mineTableView.estimatedRowHeight = 0;
_mineTableView.estimatedSectionFooterHeight = 0;
_mineTableView.estimatedSectionHeaderHeight = 0;
if (@available(iOS 11.0, *)) {
[_mineTableView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
} else {
self.automaticallyAdjustsScrollViewInsets = NO;
}
}
return _mineTableView;
}
//获取cell
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *rid = @"XUMineCellIdentify";
//如果在创建tableView的时候注册cell了,在使用dequeueReusableCellWithIdentifier获取cell的时候就不用判空了,因为注册后一定可以获取得到。
XUMineCell *cell = [tableView dequeueReusableCellWithIdentifier:rid];
//如果在创建tableView的时候没有注册cell,在获取cell的时候就需要判空。
if(cell == nil){
cell = [[XUMineCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:rid];
}
}
补充:关于tableView的tableHeaderView和tableFooterView
- 默认情况下,他们的位置布局如下:
tableHeaderView
sectionHeaderView
cell
... 其他cell
sectionFooterView
... 其他组
tableFooterrView
如果tableView有内边距,比如设置上内边距为100:UIEdgeInsets(top: 100, left: 0, bottom: 0, right: 0)
,那么它们都会被挤下去,如下图,蓝色背景的是tableHeaderView,下面分别是sectionHeaderView、cell,蓝色背景上面的就是100的内边距。
- 如果是通过frame的方式设置tableHeaderView、tableFooterView,那么tableHeaderView一直在最上方,tableFooterView一直在最下方,通过frame只能修改它们的高度。
- 如果是通过snapkit或者masonry就可以任意修改它们的位置和宽高(不知道为什么)。
- reloadData的时候会刷新tableHeaderView、tableFooterView,比如如果修改了tableHeaderView的高度,就需要reloadData后才会生效。
总结:使用tableHeaderView、tableFooterView,我们就用frame进行布局。
二. UITableView的重用机制
系统的UITableView是继承于UIScrollView,所以可以滑动,关于UITableView最主要的就是重用机制,下面验证UITableView的重用机制,其中_tableview是通过xib拖过来的,代码如下:
@interface ViewController (){
IBOutlet UITableView *_tableview;
//一共使用了多少个cell(重用池+现有池个数)
NSMutableArray *_cellAry;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.navigationBar.translucent = NO;
_cellAry = [NSMutableArray new];
_tableview.estimatedRowHeight = 0; // 预估高度,默认是44, ios11
[_tableview registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
}
#pragma mark - tableView delegate
// 646 一屏幕最多展示多少个cell:5
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 30;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(@"%ld", (long)indexPath.row);
return 200;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
BOOL isContain = NO;
for(NSValue *value in _cellAry){
if ([value.nonretainedObjectValue isEqual:cell]) {
isContain = YES;
break;
}
}
if (!isContain) {
//弱引用
//详情可参考:https://www.jianshu.com/p/51156d4ae885
NSValue *value = [NSValue valueWithNonretainedObject:cell];
[_cellAry addObject:value];
}
cell.textLabel.text = [@(indexPath.row) description];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
// 验证预估高度
// NSLog(@"%f", _tableview.contentSize.height);
NSLog(@"==%lu", (unsigned long)_cellAry.count);
}
当我们拖动tableView并且点击其中一个Cell, 会打印如下:
2019-10-22 14:10:20.631702+0800 TableviewAnly[11882:1011989] 11
2019-10-22 14:10:20.632040+0800 TableviewAnly[11882:1011989] 11
2019-10-22 14:10:21.415626+0800 TableviewAnly[11882:1011989] 12
2019-10-22 14:10:21.416440+0800 TableviewAnly[11882:1011989] 12
2019-10-22 14:14:49.007275+0800 TableviewAnly[11882:1011989] ==5
可以发现数组中一共有5个cell, 但是如下图, 最多只能展示4个Cell,另外1个Cell去哪了呢?
简析:
- 重用机制,会一直保持cell不变的数量(5)
- 问题:当界面显示4个时候, 另外1个去哪里了?
重用池(没有显示在界面上的cell: 1个)+ 现有池(visible即显示在界面上的cell: 4个)= 5个。 - 问题:是如何控制显示哪些的呢? 哪些显示在界面上,哪些不显示呢?
table在加载的时候,每个cell的位置信息被保存起来了,数据先行,UI后走(位置信息被保存下来了,那么哪些显示哪些不显示自然就知道了)。
总结:
通过上面的代码我们就知道现有池有4个cell,重用池有1个cell,所以一共5个cell,而且滚动的时候一直是5个cell,从而就验证系统UITableView的重用机制。
三. 模仿系统的UITableView
因为系统的tableView在加载的时候,每个cell的位置信息被保存起来了,所以我们先创建个模型,代码如下:
#import
#import
@interface EOCCellModel : NSObject
@property (nonatomic, assign)CGFloat y;
@property (nonatomic, assign)CGFloat height;
@end
自定义一个继承于UIScrollView的EOCTableView,代码如下:
EOCTableView.h文件
#import
@class EOCTableView;
//数据源代理
@protocol EOCTableViewDelegate
@required
- (NSInteger)tableView:(EOCTableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (CGFloat)tableView:(EOCTableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (UITableViewCell *)tableView:(EOCTableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
@end
@interface EOCTableView : UIScrollView
@property (nonatomic,weak)iddelegate; //数据源代理
//刷新cell方法
- (void)reloadData;
//重用方法
- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;
@end
EOCTableView.m文件
#import "EOCTableView.h"
#import "EOCCellModel.h"
@interface EOCTableView(){
NSMutableDictionary *_visibleCellDict; // 现有池
NSMutableArray *_reusePoolCellAry; // 重用池
NSMutableArray *_cellInfoArr; // cell 信息 (y值,高度,数量信息)
}
@end
// 667 cell高度 60, 660/60 = 11,7个像素可以显示2个残的 界面最多可以显示 11 + 2 = 13
@implementation EOCTableView
/*
1现有池
2重用池
3位置信息
*/
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
_visibleCellDict = [NSMutableDictionary new];
_reusePoolCellAry = [NSMutableArray new];
_cellInfoArr = [NSMutableArray new];
}
return self;
}
#pragma mark - 数据,UI
/*
数据, UI
*/
- (void)reloadData{
// 1 处理数据
[self dataHandle];
// 2 UI 处理
[self setNeedsLayout];
}
// 1. 处理数据, 数据model不会复用,只是保存下来
- (void)dataHandle{
// 1.1 获取cell的数量
NSInteger allCellCount = [self.delegate tableView:self numberOfRowsInSection:0];
[_cellInfoArr removeAllObjects]; // 移除旧的信息
CGFloat totalCellHeight = 0;
for (int i = 0; i < allCellCount; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
CGFloat cellHeight = [self.delegate tableView:self heightForRowAtIndexPath:indexPath];
//1.2 获取cell的高度和y值, 并保存起来
EOCCellModel *model = [EOCCellModel new];
model.y = totalCellHeight;
model.height = cellHeight;
[_cellInfoArr addObject:model];
totalCellHeight += cellHeight;
}
//根据总高度设置可以滑动的范围
[self setContentSize:CGSizeMake(self.frame.size.width, totalCellHeight)];
}
// 2. UI处理, UI会复用
- (void)layoutSubviews{
[super layoutSubviews];
// 2.1 计算可视范围,要显示哪些cell,并把相关cell显示到界面
CGFloat startY = self.contentOffset.y;
CGFloat endY = self.contentOffset.y + self.frame.size.height;
if (startY < 0) {
startY = 0;
}
if (endY > self.contentSize.height) {
endY = self.contentSize.height;
}
// 2.2 计算边界的cell索引 (从哪几个到哪几个cell, 如第3个到第9个)
EOCCellModel *startModel = [EOCCellModel new];
startModel.y = startY;
EOCCellModel *endModel = [EOCCellModel new];
endModel.y = endY;
// 2.3 目地就是获取可视区域显示cell的索引范围
//使用二分查找,替换下面的方法,效率更高
//查找:用二分查找(1024 = 2的10次方 查找次数最多10次)
NSInteger startIndex = [self binarySerchOC:_cellInfoArr target:startModel];
NSInteger endIndex = [self binarySerchOC:_cellInfoArr target:endModel];
// // 2.3.1 开始索引
// for(NSInteger i = 0; i < _cellInfoArr.count; i++){
//
// EOCCellModel *cellModel = _cellInfoArr[i];
//
// if (cellModel.y <= startY && cellModel.y + cellModel.height > startY) {
// startIndex = I;
// break;
// }
// }
// // 2.3.2 结束索引
// for (NSInteger i = startIndex + 1; i < _cellInfoArr.count; i++) {
//
// EOCCellModel *cellModel = _cellInfoArr[i];
// if (cellModel.y < endY && cellModel.y + cellModel.height >= endY) {
// endIndex = I;
// break;
// }
// }
// 2.4 UI操作 获取cell,并显示到View上
for (NSInteger i = startIndex; i <= endIndex; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
//这个代理方法里面执行了重用机制方法dequeueReusableCellWithIdentifier
UITableViewCell *cell = [self.delegate tableView:self cellForRowAtIndexPath:indexPath];
// 当row = 3 的时候, 如果现有池有个相同的CellA,就返回cellA
// 当row = 4 的时候,现有池不存相关的cell,然后去重用池读取cell,发现重用池有一个cellA,于是就返回了cellA
EOCCellModel *cellModel = _cellInfoArr[i];
cell.frame = CGRectMake(0, cellModel.y, self.frame.size.width, cellModel.height);
if (![cell superview]) {
[self addSubview:cell]; // 添加到tableview上 // addsubivew
}
}
// 2.5 从现有池里面移走不在界面上的cell,移动到重用池里(把不在可视区域的cell移到重用池)
NSArray *visibelCellKey = _visibleCellDict.allKeys;
for (NSInteger i = 0; i < visibelCellKey.count; i++) {
NSInteger index = [visibelCellKey[i] integerValue];
if (index < startIndex || index > endIndex) {
[_reusePoolCellAry addObject:_visibleCellDict[visibelCellKey[i]]];
[_visibleCellDict removeObjectForKey:visibelCellKey[I]];
}
}
}
#pragma mark - 重用的根本方法 先从现有池拿,没有再从重用池拿,没有再创建
// 重用池Model/UI 和 现有池Model/UI
// 重用池和现有池有同一个 cellA,现有池的cellA 就会返回出来
- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath{
// 1. 是否在现有池里面,如果有,从现有池里面读取对应的cell返回
UITableViewCell *cell = _visibleCellDict[@(indexPath.row)];
if(!cell){
// 2. 现有池如没有,再看重用池
// 2.1 重用池是否有没有用的cell,有就返回cell
if(_reusePoolCellAry.count > 0){
cell = _reusePoolCellAry.firstObject;
}else{
// 2.2 重用池没有就创建
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
// 2.3 保存到现有池,移除重用池
[_visibleCellDict setObject:cell forKey:@(indexPath.row)];// 保存到现有池
[_reusePoolCellAry removeObject:cell]; // 移除重用池
}
return cell;
}
#pragma mark - 二分查找查找索引操作
- (NSInteger)binarySerchOC:(NSArray*)dataAry target:(EOCCellModel*)targetModel{
NSInteger min = 0;
NSInteger max = dataAry.count - 1;
NSInteger mid;
while (min < max) {
mid = min + (max - min)/2;
// 条件判断
EOCCellModel *midModel = dataAry[mid];
if (midModel.y < targetModel.y && midModel.y + midModel.height > targetModel.y) {
return mid;
}else if(targetModel.y < midModel.y){
max = mid;// 在左边
if (max - min == 1) {
return min;
}
}else {
min = mid;// 在右边
if (max - min == 1) {
return max;
}
}
}
return -1;
}
@end
在SecondViewCtr添加如下代码:
#import "SecondViewCtr.h"
#import "EOCTableView.h"
@interface SecondViewCtr (){
EOCTableView *_tableView;
}
@end
@implementation SecondViewCtr
- (void)viewDidLoad {
[super viewDidLoad];
_tableView = [[EOCTableView alloc] initWithFrame:self.view.frame];
_tableView.delegate = self;
[_tableView reloadData];
[self.view addSubview:_tableView];
}
- (NSInteger)tableView:(EOCTableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 200;
}
//性能优化1: 假如高度都不一样 【tableView reload】去计算 重新计算200个cell的高度,计算量会很大,所以常见的优化手段就是保存高度
- (CGFloat)tableView:(EOCTableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 60;
}
- (UITableViewCell *)tableView:(EOCTableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//性能优化2:UI,cell的处理更少占用主线程(使用SDWebImage异步加载,缓存)
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
cell.textLabel.text = [@(indexPath.row) description];
return cell;
}
@end
运行后,效果图如下:
滑动后,在EOCTableView的layoutSubviews方法打个断点,po一下:
(lldb) po _visibleCellDict.allKeys.count
12
(lldb) po _reusePoolCellAry.count
2
发现现有池12个,重用池2个,实现了模仿系统的tableView。
总结:
系统tableView重用Cell的根本方法就是dequeueReusableCellWithIdentifier方法,这个方法是在系统调用cellForRowAtIndexPath方法的内部调用的,dequeueReusableCellWithIdentifier内部做的事是:先从现有池拿,没有再从重用池拿,没有再创建。
关于代码的讲解就省略了,看注释吧。
四. 系统UITableView的优化
最基本的两个方法:
- 缓存Cell高度
假如高度都不一样 [tableView reload] 去计算,重新计算200个cell的高度,计算量会很大,所以常见的优化手段就是保存高度。 - 异步加载Cell图片
Cell的处理更少占用主线程(使用SDWebImage异步加载、缓存)
代码如上:
五. 实现UITaleView悬浮两个头视图的效果
自定义taleView,代码如下:
#import
@interface TaskTableView : UITableView
@property (nonatomic, weak)UIView *secionOneHeadView;
@property (nonatomic, weak)UIView *secionTwoHeadView;
@end
#import "TaskTableView.h"
@implementation TaskTableView
- (void)layoutSubviews{
[super layoutSubviews];
// 重新布局headView位置
CGFloat headViewWidth = self.secionOneHeadView.frame.size.width;
CGFloat headViewHeight = self.secionOneHeadView.frame.size.height;
//当前面五个cell已经滑过去的时候,重新布局
if (self.contentOffset.y > 70 * 5 ) {
//重新设偏移量
self.secionOneHeadView.frame = CGRectMake(0, self.contentOffset.y, headViewWidth, headViewHeight);
self.secionTwoHeadView.frame = CGRectMake(0, self.contentOffset.y + headViewHeight, headViewWidth, headViewHeight);
}
//系统默认会移除,所以我们重新添加上去
if (![self.secionOneHeadView superview]) {
[self addSubview:self.secionOneHeadView];
}
}
@end
在ThridViewCtr.xib内容如下图:
在ThridViewCtr.m实现如下代码:
#import "ThridViewCtr.h"
#import "TaskTableView.h"
@interface ThridViewCtr (){
IBOutlet TaskTableView *_tableview;
IBOutlet UIView *_sectionOneHeadView;
IBOutlet UIView *_sectionTwoHeadView;
}
@end
@implementation ThridViewCtr
- (void)viewDidLoad {
[super viewDidLoad];
[_sectionOneHeadView removeFromSuperview];
[_sectionTwoHeadView removeFromSuperview];
_tableview.secionOneHeadView = _sectionOneHeadView;
_tableview.secionTwoHeadView = _sectionTwoHeadView;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (section == 0) {
return 5;
}
return 20;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 70;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
return 80;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
if (section == 0) {
return _sectionOneHeadView;
}else{
return _sectionTwoHeadView;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
cell.textLabel.text = [NSString stringWithFormat:@"%d--%d", indexPath.section, indexPath.row];
return cell;
}
@end
运行后向上拖动tableView发现, 两个headerView会一直停留在顶部, 效果图如下:
总结:
实现方法就是拿到头视图引用,在tableView的layoutSubviews方法里面修改头视图偏移量,再重新添加到tableView上。
六. 其他注意点
- 调用reloadData方法并不是立马执行,是异步的,会在下一个RunLoop循环里面执行。
- tableview.estimatedRowHeight = 0;
① tableView的预估高度,iOS11之后出现的,在一个cell还没有显示到界面上,系统会给一个预估高度,默认44。
② 如果这个属性不设置为0,在这个时候去获取tableview.contentSize.height是不准的。
③ 这个在MJ_footer里面,如果不设置预估高度为0,拿到的contentSize不准,会出现bug。
Demo地址:https://github.com/iamkata/tableView