作者兩年開發(fā)經驗,通過通讀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實現懸浮頂部的效果,需要選擇創(chuàng)建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
方法創(chuàng)建TableView時,必須選擇UITableViewStyle
,且之后不能修改。如果通過initWithFrame:
方法創(chuàng)建,默認創(chuàng)建的是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
[蒙層效果]創(chuàng)建一個對象賦給tableView
的separatorEffect
屬性,并且將Cell的背景色設置為clearColor
,即可獲得非??犰诺陌胪该鰿ell效果了。
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橫屏時,會根據內容留有空白。此參數用于判斷是否需要這么做。如下圖:

如果設置屬性為YES,則不會出現此情況。
4. Creating Table View Cells
Cell擁有兩種創(chuàng)建可復用Cell方式,我們將分別介紹兩種方式需要注意的地方
-registerNib:forCellReuseIdentifier:
-registerClass:forCellReuseIdentifier:
registerNib:forCellReuseIdentifier:
想利用此方法創(chuàng)建可復用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
方法創(chuàng)建新的cell。
registerClass:forCellReuseIdentifier:
如果cell并沒有通過xib繪制,可以調用此方法來創(chuàng)建可復用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
,這一點需要開發(fā)者記住。
5. When to use dequeueReusableCellWithIdentifier vs dequeueReusableCellWithIdentifier : forIndexPath
這兩個方法看上去沒有差別呀,唯一一點的區(qū)別就是后者多了一個forIndexPath。仔細閱讀api發(fā)現,前者是老方法,后者是iOS6提供的新方法。實際操作之后發(fā)現,當我們沒有通過registerClass
或registerNib
注冊cell時,調用這兩個方法,前者會返回一個nil,后者會直接崩潰。所以如果在未注冊cell的情況下,調用第一種方式創(chuàng)建可復用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
方法創(chuàng)建cell,最后都會執(zhí)行到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的高度,這簡直不能忍?。∫怯?0000行,那豈不是要卡死=。=
所以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.
此方法不會觸發(fā)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進行插入,刪除,移動和重載時,就需要引入beginUpdates
和endUpdates
方法。
- beginUpdates && - endUpdates
beginUpdates和endUpdates方法是一對綁定在一起的方法,用來對UITableView批量更新操作。先看一下多個插入刪除操作不使用beginUpdates
和endUpdates
的情況:
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];
//操作...
這種做法對于每一個操作,是嚴格按照順序執(zhí)行的,先執(zhí)行操作1,再執(zhí)行操作2…再執(zhí)行任意一個操作的時候,先更新數據源,然后調用表視圖的相關操作,該操作方法執(zhí)行完畢后,會立即調用相關的代理方法更新表視圖。更新完畢后繼續(xù)執(zhí)行下一個操作…即整個過程是不斷地調用代理方法來更新表視圖。因此對于這種按序更新的方法,每一個更新操作并不是同時進行的,如果有很多個操作,可能通過肉眼就能看出更新的先后,這通常并不是我們想要的。
通過beginUpdates和endUpdates則可以批量處理操作,區(qū)別于上面的操作一個一個執(zhí)行然后不斷更新表視圖,批量處理實現了在所有操作執(zhí)行完后調用代理方法一次性更新表視圖,這種方法保證了所有操作最終是同時更新的。
- beginUpdates和endUpdates批量更新有三部曲:
更新數據源(所有操作)
創(chuàng)建相應的indexPaths數組
執(zhí)行操作
下面舉例說明:
//更新數據源
NSMutableArray *array1 = (NSMutableArray *)self.arraySections[0];
[array1 removeObjectAtIndex:0];
[array1 removeObjectAtIndex:2];
[array1 insertObject:@"111" atIndex:1];
[array1 insertObject:@"333" atIndex:3];
//創(chuàng)建相應的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]];
//執(zhí)行操作
[self.tableMain beginUpdates];
[self.tableMain deleteRowsAtIndexPaths:indexPathsDelete withRowAnimation:UITableViewRowAnimationFade];
[self.tableMain insertRowsAtIndexPaths:indexPathsInsert withRowAnimation:UITableViewRowAnimationFade];
[self.tableMain endUpdates];
beginUpdates和endUpdates方法對之間的操作執(zhí)行后并沒有立刻更新表視圖,而是等endUpdates方法返回后才調用代理方法一次性更新的,因此所有更新動畫都是同時執(zhí)行的。
特別提醒:當刪除和插入同時執(zhí)行時,無論代碼中插入操作是否先于刪除,實際都是先執(zhí)行刪除再執(zhí)行插入等操作。
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部分,結合自己的工作經驗,列出了作者覺得重要的或以前理解不透徹的知識點。若有錯誤,請在留言處提出。謝謝。