目录
- 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行该Section
的header
以及footer
也会显示。
-
Style
有两种Style:UITableViewStylePlain
和UItableViewStyleGrouped
。当你创建一个UITableView实例必须指定其的Style、并且无法改变。
UITableViewStylePlain
1.plain类型有多段时,段头停留(自带效果)
2.plain类型默认section之间没有中间的间距和头部间距(想让plain类型的section之间留有空白,需要在UITableView代理方法中return自定义的header和footer,并在自定义的UITableViewHeaderFooterView里面重写setFrame方法)
- 可以有索引存在
UItableViewStyleGrouped
sectionHeaderView不会悬浮、存在默认高度。
剩下诸如背景色什么的区别看这篇吧、反正都是定制基本不用考虑《iOS UITableView 的 Plain和Grouped样式的区别》
复用原理
可以参考一下:《UITableView的Cell复用原理和源码分析》
写的是mac_os的Chameleon
、但原理应该都差不多。
简而言之:
- 用数组
_cachedCells
存储当前屏幕能展示的cell
通过CGRectIntersectsRect(cellRect,visibleBounds)
确定 - 滑动时、获取移出屏幕的cell
对_cachedCells
进行copy
、然后remove掉当前屏幕能展示的cell。剩下的cell就是消失的cell。 - 存入复用池
将上一步获取的cell存入集合_reusableCells
(当然我认为用字典也行)。 - 取出cell
从_reusableCells
取出特定cell并且移出
至于是不是会在移出屏幕时从父视图remove掉、这个demo里是做了。但是真正的TableVIew似乎有一些不为人知的规则(大概率是总remove会造成性能损耗)、并不是一定会立刻remove。
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
方法
如果复用池中没有、则有两种情况:
tableView中已经注册了cell
直接新建cell、并调用cell的initWithStyle:reuseIdentifier:
创建没有注册cell
直接崩溃这里要与dequeueReusableCellWithIdentifier:
进行区分`。
-
- dequeueReusableCellWithIdentifier:
返回指定的id的cell
- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
和上一个方法基本相同。
-
二者的异同
相同点:
- 二者首先都会去从重用池(
cellForRow返回的cell在移出屏幕时会被加入重用池
)查找。 - 如果重用池中没有、再尝试从注册表查找。
不同点:
- 如果没有注册、他会返回nil。而前者会崩溃
- 他只是取出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
- 已经进入复用池
- 超出范围的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则禁用。
这里有一些需要注意的地方:
禁用预估功能
tableView会每次都读取所有indexPath
的heightForRowAtIndexPath
进行行高计算。开启预估功能
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];
}
需要注意的是beginUpdates
与enpdate
是串行的。
所以、我们最好先操作数据源然后再进行操作。
-
- 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;
- 尽量使用
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;
- 如果指定了可视的IndexPath。这个方法会立即向数据源请求新的单元格。
- 新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;