目錄
- UITableView
- 復用原理
- UITableViewDataSource&&UITableViewDelegate
-
配置TableView
- style
- numberOfRowsInSection:
- numberOfSections
- rowHeight
- separator
- backgroundView
- cellLayoutMarginsFollowReadableWidth
-
創(chuàng)建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
- 視圖區(qū)域
- rectForSection:
- rectForRowAtIndexPath:
- 預加載
- 索引配置
- 通知
- UITableViewSelectionDidChangeNotification
UITableView
-
SuperClass為UIScrollView
但只允許縱向滾動。
-
Section(節(jié))
可以為0節(jié)惑淳、每個Section
都有自己的Row
循未。
任何section
都可以有選擇地在前面的一個header
涩盾,并且可以選擇后跟一個footer
。
-
Row(行)
可以為0行塔沃、不過即使為0行該Section
的header
以及footer
也會顯示。
-
Style
有兩種Style:UITableViewStylePlain
和UItableViewStyleGrouped
。當你創(chuàng)建一個UITableView實例必須指定其的Style双絮、并且無法改變。
UITableViewStylePlain
1.plain類型有多段時得问,段頭停留(自帶效果)
2.plain類型默認section之間沒有中間的間距和頭部間距(想讓plain類型的section之間留有空白囤攀,需要在UITableView代理方法中return自定義的header和footer,并在自定義的UITableViewHeaderFooterView里面重寫setFrame方法)
- 可以有索引存在
UItableViewStyleGrouped
sectionHeaderView不會懸浮宫纬、存在默認高度焚挠。
剩下諸如背景色什么的區(qū)別看這篇吧、反正都是定制基本不用考慮《iOS UITableView 的 Plain和Grouped樣式的區(qū)別》
復用原理
可以參考一下:《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似乎有一些不為人知的規(guī)則(大概率是總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
單元格的統(tǒng)一高度
@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開發(fā)時調整cell的margin用
@property(nonatomic) BOOL cellLayoutMarginsFollowReadableWidth;
在iPad開發(fā)時,使用iOS 9系統(tǒng)會出現tableviewCell的位置變化誊垢,在開發(fā)中默認與右側是15個像素掉弛,可是現在明顯大的多。需要將其置NO喂走。
創(chuàng)建Cell
-
registerCell
//注冊nib創(chuàng)建的cell
- (void)registerNib:(UINib *)nib
forCellReuseIdentifier:(NSString *)identifier;
//注冊代碼創(chuàng)建的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:
創(chuàng)建沒有注冊cell
直接崩潰這里要與dequeueReusableCellWithIdentifier:
進行區(qū)分`。
-
- 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
將會導不發(fā)生滾動伐蒋。如果想要這種效果工三、你需要自己實現滾動代碼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的編輯狀態(tài)
//屬性版
@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執(zhí)行插入動畫授段,舊cell執(zhí)行移出動畫
-
- reloadSectionIndexTitles
刷新右側索引
- (void)reloadSectionIndexTitles;
視圖區(qū)域
-
- rectForSection:
返回指定section所占的范圍
- (CGRect)rectForSection:(NSInteger)section;
需要注意這個方法的準確性、取決于預估行高如何去設置番甩。
-
- rectForRowAtIndexPath:
返回指定index所占的范圍
- (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath;
如果該cell還沒有被展示過侵贵、系統(tǒng)會去獲取正確的高度。(也就是調用heightForRowAtIndexPath
甚至cellForRowAtIndexPath
)
indexPath違規(guī)則返回CGRectZero
還有兩個方法缘薛、懶得測試了...
- (CGRect)rectForFooterInSection:(NSInteger)section;
- (CGRect)rectForHeaderInSection:(NSInteger)section;
預加載
-
prefetchDataSourc
通過Pre-Fetching功能預知用戶行為
@property(nonatomic, weak) id<UITableViewDataSourcePrefetching> prefetchDataSource;
這個協(xié)議其實是用來通知我們窍育,當前滑動到某個區(qū)域后,根據這次滑動的方向接下去可能還會滑向哪些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
選擇項發(fā)生更改時發(fā)送通知
const NSNotificationName UITableViewSelectionDidChangeNotification;