跟著蘋果API學習UITableView

作者兩年開發(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)建一個對象賦給tableViewseparatorEffect屬性,并且將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橫屏時,會根據內容留有空白。此參數用于判斷是否需要這么做。如下圖:


ipad橫屏
ipad橫屏



如果設置屬性為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ā)現,當我們沒有通過registerClassregisterNib注冊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進行插入,刪除,移動和重載時,就需要引入beginUpdatesendUpdates方法。

- beginUpdates && - endUpdates

beginUpdates和endUpdates方法是一對綁定在一起的方法,用來對UITableView批量更新操作。先看一下多個插入刪除操作不使用beginUpdatesendUpdates的情況:

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部分,結合自己的工作經驗,列出了作者覺得重要的或以前理解不透徹的知識點。若有錯誤,請在留言處提出。謝謝。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容