iOS文档补完计划--UITableView

目录

  • UITableView
  • 复用原理
  • UITableViewDataSource&&UITableViewDelegate
  • 配置TableView
    • style
    • numberOfRowsInSection:
    • numberOfSections
    • rowHeight
    • separator
    • backgroundView
    • cellLayoutMarginsFollowReadableWidth
  • 创建Cell
    • registerCell
    • dequeueReusableCellWithIdentifier:forIndexPath:
    • dequeueReusableCellWithIdentifier:
  • 页眉和页脚
  • Cell&&IndexPath
    • cellForRowAtIndexPath:
    • indexPathForCell:
    • indexPathForRowAtPoint:
    • indexPathsForRowsInRect:
    • visibleCells
    • indexPathsForVisibleRows
  • 预估高度
  • 滚动tableView
    • scrollToRowAtIndexPath:atScrollPosition:animated:
    • scrollToNearestSelectedRowAtScrollPosition:animated:
  • 选择控制
    • indexPathForSelectedRow
    • indexPathsForSelectedRows
    • selectRowAtIndexPath:animated:scrollPosition:
    • deselectRowAtIndexPath:animated:
    • 选择权限
  • 插入&&删除&&移动
    • performBatchUpdates:completion:
  • 编辑TableView
  • 重载
    • hasUncommittedUpdates
    • reloadData
    • reloadRowsAtIndexPaths:withRowAnimation:
    • reloadSections:withRowAnimation:
    • reloadSectionIndexTitles
  • 视图区域
    • rectForSection:
    • rectForRowAtIndexPath:
  • 预加载
  • 索引配置
  • 通知
    • UITableViewSelectionDidChangeNotification

UITableView

  • SuperClass为UIScrollView

但只允许纵向滚动。

  • Section(节)

可以为0节、每个Section都有自己的Row
任何section都可以有选择地在前面的一个header,并且可以选择后跟一个footer

  • Row(行)

可以为0行、不过即使为0行该Sectionheader以及footer也会显示。

  • Style

有两种Style:UITableViewStylePlainUItableViewStyleGrouped。当你创建一个UITableView实例必须指定其的Style、并且无法改变。

UITableViewStylePlain

1.plain类型有多段时,段头停留(自带效果)
2.plain类型默认section之间没有中间的间距和头部间距(想让plain类型的section之间留有空白,需要在UITableView代理方法中return自定义的header和footer,并在自定义的UITableViewHeaderFooterView里面重写setFrame方法)

  1. 可以有索引存在
UItableViewStyleGrouped

sectionHeaderView不会悬浮、存在默认高度。

剩下诸如背景色什么的区别看这篇吧、反正都是定制基本不用考虑《iOS UITableView 的 Plain和Grouped样式的区别》


复用原理

可以参考一下:《UITableView的Cell复用原理和源码分析》
写的是mac_os的Chameleon、但原理应该都差不多。


UITableViewDataSource&&UITableViewDelegate

《iOS文档补完计划--UITableViewDataSource&&UITableViewDelegate》


配置TableView

  • style

返回初始化时设置的style

@property(nonatomic, readonly) UITableViewStyle style;

如果初始化时没有设置、默认为UITableViewStylePlain

  • - numberOfRowsInSection:

返回指定section中的row数

- (NSInteger)numberOfRowsInSection:(NSInteger)section;

在每次reload中、这个值会通过UITableViewDataSource获取并被缓存。

  • - numberOfSections

返回总计的section数

@property(nonatomic, readonly) NSInteger numberOfSections;

在每次reload中、这个值会通过UITableViewDataSource获取并被缓存。

  • rowHeight

单元格的统一高度

@property(nonatomic) CGFloat rowHeight;

一旦主动实现tableView:heightForRowAtIndexPath:这个值将会失效。并且在每一次显示cell时调用该方法、这样对性能会有一定影响。

默认值是UITableViewAutomaticDimension、也就是自适应。不过如果你使用ib生成控件、需要显示的设置rowHeight = UITableViewAutomaticDimension以自适应。

  • separator

分割线的样式、颜色、效果

//分割线样式 默认UITableViewCellSeparatorStyleSingleLine
@property(nonatomic) UITableViewCellSeparatorStyle separatorStyle;
//分割线颜色 默认`gray`
@property(nonatomic, strong) UIColor *separatorColor;
//分隔效果
@property(nonatomic, copy) UIVisualEffect *separatorEffect;

//分割线的位置
@property(nonatomic) UIEdgeInsets separatorInset;

//iOS11之后对separatorInset的扩展
@property(nonatomic) UITableViewSeparatorInsetReference separatorInsetReference;

不过通常我们都是自定义、所以直接
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;一劳永逸

  • backgroundView

TableView的背景View

@property(nonatomic, strong) UIView *backgroundView;

默认为nil。
你可以在一些情况下用到它、比如将一个图片作为backgroundView。
如果设置背景色直接使用backgroundView即可

  • cellLayoutMarginsFollowReadableWidth

pad开发时调整cell的margin用

@property(nonatomic) BOOL cellLayoutMarginsFollowReadableWidth;

在iPad开发时,使用iOS 9系统会出现tableviewCell的位置变化,在开发中默认与右侧是15个像素,可是现在明显大的多。需要将其置NO。


创建Cell

  • registerCell
//注册nib创建的cell
- (void)registerNib:(UINib *)nib 
forCellReuseIdentifier:(NSString *)identifier;

//注册代码创建的cell
- (void)registerClass:(Class)cellClass 
forCellReuseIdentifier:(NSString *)identifier;

你可以通过将Nib传入nil来复位注册表

  • - dequeueReusableCellWithIdentifier:forIndexPath:

返回已经注册的指定cell、并添加到tableView的指定位置中

- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier 
                                                   forIndexPath:(NSIndexPath *)indexPath;

复用池中有该cell则直接返回该cell、并调用cell的prepareForReuse方法
如果复用池中没有、则有两种情况:

  1. tableView中已经注册了cell
    直接新建cell、并调用cell的initWithStyle:reuseIdentifier:创建

  2. 没有注册cell
    直接崩溃这里要与dequeueReusableCellWithIdentifier:进行区分`。

  • - dequeueReusableCellWithIdentifier:

返回指定的id的cell

- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;

和上一个方法基本相同。

  • 二者的异同
相同点:
  1. 二者首先都会去从重用池(cellForRow返回的cell在移出屏幕时会被加入重用池)查找。
  2. 如果重用池中没有、再尝试从注册表查找。
不同点:
  1. 如果没有注册、他会返回nil。而前者会崩溃
  2. 他只是取出cell、前者会附加一个添加到TableView的操作(因为必然返回cell吧?)。

页眉和页脚

页眉页脚的注册、提取、插入。

//注册
- (void)registerNib:(nullable UINib *)nib forHeaderFooterViewReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
- (void)registerClass:(nullable Class)aClass forHeaderFooterViewReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);

//获取
- (nullable __kindof UITableViewHeaderFooterView *)dequeueReusableHeaderFooterViewWithIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0); 

//页眉同一高度
@property(nonatomic) CGFloat sectionHeaderHeight;

//页脚同一高度
@property(nonatomic) CGFloat sectionFooterHeight;


//整个TableView的页眉
@property(nonatomic, strong) UIView *tableHeaderView;

//整个TableView的页脚
@property(nonatomic, strong) UIView *tableFooterView;

Cell&&IndexPath

  • - cellForRowAtIndexPath:

返回指定indexPath位置的cell

- (__kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;

有两种情况会返回nil

  1. 已经进入复用池
  2. 超出范围的indexPath
  • - indexPathForCell:

返回指定cell的indexPath索引

- (NSIndexPath *)indexPathForCell:(UITableViewCell *)cell;

如果cell已经进入复用池、也会返回nil

  • - indexPathForRowAtPoint:

返回指定的点所在的IndexPath

- (NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point;

超出了TableVIew的bounds会返回nil

  • - indexPathsForRowsInRect:

返回rect所包含的IndexPath

- (NSArray<NSIndexPath *> *)indexPathsForRowsInRect:(CGRect)rect;
  • - visibleCells

返回当前可见的所有cell

@property(nonatomic, readonly) NSArray<__kindof UITableViewCell *> *visibleCells;
  • - indexPathsForVisibleRows

返回所有可见的IndexPath

@property(nonatomic, readonly) NSArray<NSIndexPath *> *indexPathsForVisibleRows;

预估高度

Cell、Header、Footer

@property (nonatomic) CGFloat estimatedRowHeight NS_AVAILABLE_IOS(7_0); 
@property (nonatomic) CGFloat estimatedSectionHeaderHeight NS_AVAILABLE_IOS(7_0); 
@property (nonatomic) CGFloat estimatedSectionFooterHeight NS_AVAILABLE_IOS(7_0); 

默认UITableViewAutomaticDimension、设置成0则禁用。

这里有一些需要注意的地方:
  1. 禁用预估功能
    tableView会每次都读取所有indexPathheightForRowAtIndexPath进行行高计算。

  2. 开启预估功能
    rectForSection方法返回的rect也为预估值。只有当你划过所有的cell、才会得到真实的值。


滚动tableView

  • - scrollToRowAtIndexPath:atScrollPosition:animated:

将TableView的某个IndexPath滚动到特定位置

- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath 
              atScrollPosition:(UITableViewScrollPosition)scrollPosition 
                      animated:(BOOL)animated;

UITableViewScrollPosition为一个枚举类型、表示滚动到屏幕的何处

typedef NS_ENUM(NSInteger, UITableViewScrollPosition) {
    UITableViewScrollPositionNone,//只保证能出现在屏幕上
    UITableViewScrollPositionTop,    //上
    UITableViewScrollPositionMiddle,   //中
    UITableViewScrollPositionBottom//下
}; 

文档上说该方法不会引起scrollViewDidScroll:的调用、但我测试是会调用的。存疑吧

  • - scrollToNearestSelectedRowAtScrollPosition:animated:

让选定的行滚动到屏幕指定位置

- (void)scrollToNearestSelectedRowAtScrollPosition:(UITableViewScrollPosition)scrollPosition 
                                          animated:(BOOL)animated;

比如自己实现一个滚轮、点击的cell滚动到中间。


选择控制

  • indexPathForSelectedRow

返回所选行的IndexPath

@property(nonatomic, readonly) NSIndexPath *indexPathForSelectedRow;

如果有多个选择、则返回第一个

  • indexPathsForSelectedRows

多选用

@property(nonatomic, readonly) NSArray<NSIndexPath *> *indexPathsForSelectedRows;
  • - selectRowAtIndexPath:animated:scrollPosition:

选择指定行、可以有动画

- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath 
                    animated:(BOOL)animated 
              scrollPosition:(UITableViewScrollPosition)scrollPosition;

这个方法不会引起tableView:willSelectRowAtIndexPath:tableView:didSelectRowAtIndexPath:UITableViewSelectionDidChangeNotification的调用。

如果传递UITableViewScrollPositionNone将会导不发生滚动。如果想要这种效果、你需要自己实现滚动代码scrollToRowAtIndexPath:atScrollPosition:animated:

  • - deselectRowAtIndexPath:animated:

取消选择、可以有动画

- (void)deselectRowAtIndexPath:(NSIndexPath *)indexPath 
                      animated:(BOOL)animated;

与上一个方法一样、它也不会导致一些相关方法被调用。(tableView:willDeselectRowAtIndexPath:tableView:didDeselectRowAtIndexPath:UITableViewSelectionDidChangeNotification)
并且不会产生任何滚动效果。

  • 选择权限

普通选择、编辑选择与多选

//正常模式下是否允许选择。默认YES
@property(nonatomic) BOOL allowsSelection
//正常模式下的多行选择。默认NO。可以与indexPathsForSelectedRows配合使用
@property(nonatomic) BOOL allowsMultipleSelection;
//编辑模式下是否允许选择。默认YES
@property(nonatomic) BOOL allowsSelectionDuringEditing;
//编辑模式下多选。默认NO
@property(nonatomic) BOOL allowsMultipleSelectionDuringEditing;

插入&&删除&&移动

一下方法会导致TableView会立即调用相关代理方法、以获取内容。

同时、我们可以批量操作

- (void)testTableViewUpdateCrash {
    NSIndexPath *insertIndexPath = [NSIndexPath indexPathForRow:10 inSection:0];
    NSIndexPath *deleteIndexPath = [NSIndexPath indexPathForRow:11 inSection:0];
    NSIndexPath *reloadIndexPath = [NSIndexPath indexPathForRow:12 inSection:0];
    NSIndexPath *moved1IndexPath = [NSIndexPath indexPathForRow:13 inSection:0];
    NSIndexPath *moved2IndexPath = [NSIndexPath indexPathForRow:14 inSection:0];
    [self.tableView beginUpdates];
    [self.tableView insertRowsAtIndexPaths:@[insertIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView deleteRowsAtIndexPaths:@[deleteIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView reloadRowsAtIndexPaths:@[reloadIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView moveRowAtIndexPath:moved1IndexPath toIndexPath:moved2IndexPath];
    [self.tableView endUpdates];
}

需要注意的是beginUpdatesenpdate是串行的。

所以、我们最好先操作数据源然后再进行操作。

  • - insertRowsAtIndexPaths:withRowAnimation:

插入。可以有动画效果

//插入
- (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths 
              withRowAnimation:(UITableViewRowAnimation)animation;

//删除
- (void)deleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths 
              withRowAnimation:(UITableViewRowAnimation)animation;

//移动-不允许动画、每次移动单行
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath 
               toIndexPath:(NSIndexPath *)newIndexPath;

//插入sections
- (void)insertSections:(NSIndexSet *)sections 
      withRowAnimation:(UITableViewRowAnimation)animation;

//删除sections
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
//移动sections
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
  • - performBatchUpdates:completion:

将多个操作放入动画组进行。iOS11之后

- (void)performBatchUpdates:(void (^)(void))updates 
                 completion:(void (^)(BOOL finished))completion;
  • - beginUpdates&&endUpdates

准备一系列插入、删除或选择等操作

- (void)beginUpdates;
- (void)endUpdates;
  1. 尽量使用performBatchUpdates:completion:而不是这个方法

编辑TableView

修改TableView的编辑状态

//属性版
@property(nonatomic, getter=isEditing) BOOL editing;
//动画版
- (void)setEditing:(BOOL)editing 
          animated:(BOOL)animated;

具体使用可以参阅《iOS文档补完计划--UITableViewDataSource&&UITableViewDelegate》中关于插入、删除、拖动的实现。


重载

  • hasUncommittedUpdates

Drag & Drop相关。当返回YES时不建议进行reload

@property(nonatomic, readonly) BOOL hasUncommittedUpdates;
  • - reloadData

重载整个TableVIew

- (void)reloadData;

不应该在插入或删除行的方法中调用,特别是在用beginUpdates和enpdate调用实现的动画块中。

  • - reloadRowsAtIndexPaths:withRowAnimation:

允许使用动画重载指定的indexPath

- (void)reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths 
              withRowAnimation:(UITableViewRowAnimation)animation;
  • - reloadSections:withRowAnimation:

重载指定的section

- (void)reloadSections:(NSIndexSet *)sections 
      withRowAnimation:(UITableViewRowAnimation)animation;
  1. 如果指定了可视的IndexPath。这个方法会立即向数据源请求新的单元格。
  2. 新cell执行插入动画,旧cell执行移出动画
  • - reloadSectionIndexTitles

刷新右侧索引

- (void)reloadSectionIndexTitles;

视图区域

  • - rectForSection:

返回指定section所占的范围

- (CGRect)rectForSection:(NSInteger)section;

需要注意这个方法的准确性、取决于预估行高如何去设置。

  • - rectForRowAtIndexPath:

返回指定index所占的范围

- (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath;

如果该cell还没有被展示过、系统会去获取正确的高度。(也就是调用heightForRowAtIndexPath甚至cellForRowAtIndexPath)

indexPath违规则返回CGRectZero

还有两个方法、懒得测试了...

- (CGRect)rectForFooterInSection:(NSInteger)section;
- (CGRect)rectForHeaderInSection:(NSInteger)section;

预加载

  • prefetchDataSourc

通过Pre-Fetching功能预知用户行为

@property(nonatomic, weak) id<UITableViewDataSourcePrefetching> prefetchDataSource;

这个协议其实是用来通知我们,当前滑动到某个区域后,根据这次滑动的方向接下去可能还会滑向哪些indexPaths。好让我们做一些数据上的预备或者销毁。

单个id的复用池为队列reusable-cell queue.


索引配置

索引行数

@property (nonatomic) NSInteger sectionIndexMinimumDisplayRowCount;                                                      // 当行数达到某个数值,则显示索引栏
@property (nonatomic, strong, nullable) UIColor *sectionIndexColor NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;                   // 索引颜色
@property (nonatomic, strong, nullable) UIColor *sectionIndexBackgroundColor NS_AVAILABLE_IOS(7_0) UI_APPEARANCE_SELECTOR;         // 正常情况下的背景色
@property (nonatomic, strong, nullable) UIColor *sectionIndexTrackingBackgroundColor NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR; // 触摸时的背景色

通知

  • UITableViewSelectionDidChangeNotification

选择项发生更改时发送通知

const NSNotificationName UITableViewSelectionDidChangeNotification;

参考资料

官方文档--UITableView
iOS_10中UICollectionView和UITableView的变化

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章