跟着苹果API学习UITableView

作者两年开发经验,通过通读api重温TableView,并记录

作者:Roger


1. header悬浮顶部


  • UITableViewStylePlain

    A plain table view. Any section headers or footers are displayed as inline separators and float when the table view is scrolled.

  • UITableViewStyleGrouped

    A table view whose sections present distinct groups of rows. The section headers and footers do not float.



如果需要Section header实现悬浮顶部的效果,需要选择创建UITableViewStylePlain类型的Cell,并且实现代理方法

 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{ };
 - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{ };

如果选择UITableViewStyleGrouped类型,Header会随着Cell一同上下滑动(以上逻辑同样适用于Footer)。


另外需要知道的一点,如果用户通过

  - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style

方法创建TableView时,必须选择UITableViewStyle,且之后不能修改。如果通过initWithFrame:方法创建,默认创建的是UITableViewStylePlain类型的Cell。


2. separator[分离器]


UITableView拥有四个与separator相关的属性,分别是:

separatorEffect

separatorColor

separatorStyle

separatorInset

separatorEffect

首先来看一下iOS8加入的新属性eparatorEffect

tableview.backgroundView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"detail.jpg"]];
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
UIVibrancyEffect *vibrancyEffect = [UIVibrancyEffect effectForBlurEffect:blurEffect];
tableview.separatorEffect = vibrancyEffect;

tableView设置一张背景图,然后通过UIBlurEffect[蒙层效果]创建一个对象赋给tableViewseparatorEffect属性,并且将Cell的背景色设置为clearColor,即可获得非常酷炫的半透明Cell效果了。

separatorInset

其次,有时候我们想将Cell的分割线置顶,会使用separatorInset属性,separatorInset只能设置左右边距的与屏幕的距离[上下是不能改变的],并且现在Cell边界线始终与屏幕左侧有一段距离,通过以下方法可以将分割线置顶:

-(void)viewDidLayoutSubviews{
    if ([_tableview respondsToSelector:@selector(setSeparatorInset:)]) {
        [_tableview setSeparatorInset:UIEdgeInsetsMake(0,0,0,0)];
    }
    if ([_tableview respondsToSelector:@selector(setLayoutMargins:)]) {
        [_tableview setLayoutMargins:UIEdgeInsetsMake(0,0,0,0)];
    }
}

-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    if ([cell respondsToSelector:@selector(setSeparatorInset:)]) {
        [cell setSeparatorInset:UIEdgeInsetsZero];
    }
    if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
        [cell setLayoutMargins:UIEdgeInsetsZero];
    }
}


3. cellLayoutMarginsFollowReadableWidth


在Ipad上,当tableView横屏时,会根据内容留有空白。此参数用于判断是否需要这么做。如下图:


ipad横屏
ipad横屏



如果设置属性为YES,则不会出现此情况。


4. Creating Table View Cells


Cell拥有两种创建可复用Cell方式,我们将分别介绍两种方式需要注意的地方

-registerNib:forCellReuseIdentifier:

-registerClass:forCellReuseIdentifier:

registerNib:forCellReuseIdentifier:

想利用此方法创建可复用Cell,首先必须是xib构建的cell,其次需要注册Cell

[_tableview registerNib:[UINib nibWithNibName:@"TableViewCell" bundle:nil] forCellReuseIdentifier:@"firstCell"];

最后实现tableview的delegate方法

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    TableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"firstCell"];
    return cell;
}
需要注意的地方:

用户可以通过重写-(void)awakeFromNib{}方法来绘制cell。并且cell如果没有找到复用时,会通过-(instancetype)initWithCoder:(NSCoder *)aDecoder方法创建新的cell。


registerClass:forCellReuseIdentifier:

如果cell并没有通过xib绘制,可以调用此方法来创建可复用cell。

[_tableview registerClass:[TableViewCell class] forCellReuseIdentifier:@"third"];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    TableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"third"];
    return cell;
}
需要注意的地方:

因为Cell不是通过xib绘制的,所以不会调用-(void)awakeFromNib{}方法,所以如果想要重写cell,可以调用(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier,这一点需要开发者记住。


5. When to use dequeueReusableCellWithIdentifier vs dequeueReusableCellWithIdentifier : forIndexPath


这两个方法看上去没有差别呀,唯一一点的区别就是后者多了一个forIndexPath。仔细阅读api发现,前者是老方法,后者是iOS6提供的新方法。实际操作之后发现,当我们没有通过registerClassregisterNib注册cell时,调用这两个方法,前者会返回一个nil,后者会直接崩溃。所以如果在未注册cell的情况下,调用第一种方式创建可复用cell时,需要这么处理:

FirstTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"first"];
    if (cell == nil) {
        cell = [[FirstTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"first"];
    }
    return cell;
}

这还没有完,还需要重写cell的- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier方法。需要注意的是,无论上面的代码里调用的是init,initWithFrame方法创建cell,最后都会执行到initWithStyle这个方法里面来。
不过话说回来,如果做到每次使用cell前都注册cell,那么这两个方法就没有差别了。[这样的理解是基于以上的认识得出的,不一定正确,有待验证]
扩展阅读 :
When to use dequeueReusableCellWithIdentifier vs dequeueReusableCellWithIdentifier : forIndexPath


6. Accessing Header && Footer Views


tableHeaderView && tableFooterView

The table header/footer view is different from a section header/footer.

sectionHeaderHeight && sectionFooterHeight

This nonnegative value is used only in section group tables and only if the delegate doesn't implement the tableView:heightForFooterInSection: method.

headerViewForSection && footerViewForSection in [UITableViewHeaderFooterView]

An index number that identifies a section of the table. Table views in a plain style have a section index of zero.


7. Accessing Cells and Sections


cellForRowAtIndexPath: && - indexPathForCell:

总之记住,可以通过位置获取对应的cell,反之也可以通过cell获取它对应的位置。

- visibleCells: and - indexPathsForVisibleRows:

分别是获取屏幕内的所有cell和所有cell的位置。


8. Estimating Element Heights


estimatedRowHeight && estimatedSectionHeaderHeight && estimatedSectionFooterHeight

设置estimatedRowHeight以减少首次显示的计算量

默认情况下,首次显示之前,系统都会一次性全部计算出所有Cell的高度,这简直不能忍啊!要是有10000行,那岂不是要卡死=。=

所以iOS 7以后,UITableView有了一个新的属性:estimatedRowHeight。

从属性名上就可以看出,这个属性可以为Cell预先指定一个“估计”的高度值,这样的话,系统就可以先按照估计值布局,然后只获取显示范围内的Cell的高度,这样就不会一次性计算全部的了。

iOS7 and later ,使用此方法可以自动布局cell的高度,十分便捷。你只需要加入如下代码:

_tableview.estimatedRowHeight = 44;
_tableview.rowHeight = UITableViewAutomaticDimension;

estimatedRowHeight默认值为0,即不使用预估cell高度功能。另外在iOS8系统中rowHeight的默认值已经设置成了UITableViewAutomaticDimension,所以第二行代码可以省略。


9. Scrolling the Table View


- scrollToRowAtIndexPath:atScrollPosition:animated:

Invoking this method does not cause the delegate to receive a scrollViewDidScroll: message, as is normal for programmatically invoked user interface operations.


此方法不会触发scrollViewDidScroll:方法。
调用此方法,让tableView滚动到指定的行数,第二个参数的作用是让此cell显示在屏幕的顶/中/低部。

如果某个cell在屏幕内只展示了一部分,当用户点击这个cell时,我们希望这个cell自动移动使其能展示完整,这时我们只需要调用这个方法并且传入UITableViewScrollPositionNone这个参数。

- scrollToNearestSelectedRowAtScrollPosition:animated:

让点击的cell显示在屏幕的顶/中/低部。


10. Managing Selections


- indexPathForSelectedRow && indexPathsForSelectedRows

要使用这两个属性,首先需要设置属性allowsSelection = YES && allowsMultipleSelection = YES。类似属性还有allowsSelectionDuringEditing && allowsMultipleSelectionDuringEditing


11. Inserting, Deleting, and Moving Rows and Sections


在对cell进行插入,删除,移动和重载时,api为我们提供了一系列的方法:

– insertRowsAtIndexPaths:withRowAnimation:

– deleteRowsAtIndexPaths:withRowAnimation:

– moveRowAtIndexPath:toIndexPath:

– reloadRowsAtIndexPaths:withRowAnimation:


– insertSectionsAtIndexPaths:withRowAnimation:

– deleteSections:withRowAnimation:

– moveSection:toSection:

– reloadSections:withRowAnimation:

当我们在同一时刻(点击某按钮时),对多个cell进行插入,删除,移动和重载时,就需要引入beginUpdatesendUpdates方法。

- beginUpdates && - endUpdates

beginUpdates和endUpdates方法是一对绑定在一起的方法,用来对UITableView批量更新操作。先看一下多个插入删除操作不使用beginUpdatesendUpdates的情况:

NSMutableArray *array1 = (NSMutableArray *)self.arraySections[0];
//操作1 删除
[array1 removeObjectAtIndex:0];
[self.tableMain deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
//操作2 插入
[array1 insertObject:@"insert" atIndex:3];
[self.tableMain insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:3 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
//操作...

这种做法对于每一个操作,是严格按照顺序执行的,先执行操作1,再执行操作2…再执行任意一个操作的时候,先更新数据源,然后调用表视图的相关操作,该操作方法执行完毕后,会立即调用相关的代理方法更新表视图。更新完毕后继续执行下一个操作…即整个过程是不断地调用代理方法来更新表视图。因此对于这种按序更新的方法,每一个更新操作并不是同时进行的,如果有很多个操作,可能通过肉眼就能看出更新的先后,这通常并不是我们想要的。

通过beginUpdates和endUpdates则可以批量处理操作,区别于上面的操作一个一个执行然后不断更新表视图,批量处理实现了在所有操作执行完后调用代理方法一次性更新表视图,这种方法保证了所有操作最终是同时更新的。

  • beginUpdates和endUpdates批量更新有三部曲:

更新数据源(所有操作)
创建相应的indexPaths数组
执行操作
下面举例说明:

//更新数据源
    NSMutableArray *array1 = (NSMutableArray *)self.arraySections[0];
    [array1 removeObjectAtIndex:0];
    [array1 removeObjectAtIndex:2];
    [array1 insertObject:@"111" atIndex:1];
    [array1 insertObject:@"333" atIndex:3];

    //创建相应的indexPaths数组
    NSArray *indexPathsDelete = @[[NSIndexPath indexPathForRow:0 inSection:0],[NSIndexPath indexPathForRow:2 inSection:0]];
    NSArray *indexPathsInsert = @[[NSIndexPath indexPathForRow:1 inSection:0],[NSIndexPath indexPathForRow:3 inSection:0]];

    //执行操作
    [self.tableMain beginUpdates];
    [self.tableMain deleteRowsAtIndexPaths:indexPathsDelete withRowAnimation:UITableViewRowAnimationFade];

    [self.tableMain insertRowsAtIndexPaths:indexPathsInsert withRowAnimation:UITableViewRowAnimationFade];
    [self.tableMain endUpdates];

beginUpdates和endUpdates方法对之间的操作执行后并没有立刻更新表视图,而是等endUpdates方法返回后才调用代理方法一次性更新的,因此所有更新动画都是同时执行的。

特别提醒:当删除和插入同时执行时,无论代码中插入操作是否先于删除,实际都是先执行删除再执行插入等操作。


12. Reloading the Table View


- reloadData

It should not be called in the methods that insert or delete rows, especially within an animation block implemented with calls to beginUpdates and endUpdates.


13. Notifications


UITableViewSelectionDidChangeNotification

Posted when the selected row in the posting table view changes.
There is no userInfo dictionary associated with this notification.
没有使用过,但是需要有一个印象。


p.s

以上是作者通过阅读API tableview部分,结合自己的工作经验,列出了作者觉得重要的或以前理解不透彻的知识点。若有错误,请在留言处提出。谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,302评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,563评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,433评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,628评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,467评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,354评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,777评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,419评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,725评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,768评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,543评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,387评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,794评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,032评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,305评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,741评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,946评论 2 336

推荐阅读更多精彩内容