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的變化

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