UITableView 学习笔记
http://www.cnblogs.com/smileEvday/archive/2012/06/28/tableView.html
?
UITableView學(xué)習(xí)筆記
作者:一片楓葉
看TableView的資料其實已經(jīng)蠻久了,一直想寫點(diǎn)兒東西,卻總是因為各種原因拖延,今天晚上有時間靜下心來記錄一些最近學(xué)習(xí)的TableView的知識。下面進(jìn)入正題,UITableView堪稱UIKit里面最復(fù)雜的一個控件了,使用起來不算難,但是要用好并不容易。當(dāng)使用的時候我們必須要考慮到后臺數(shù)據(jù)的設(shè)計,tableViewCell的設(shè)計和重用以及tableView的效率等問題。
下面分9個方面進(jìn)行介紹:
一、UITableView概述
UITableView繼承自UIScrollView,可以表現(xiàn)為Plain和Grouped兩種風(fēng)格,分別如下圖所示:
其中左邊的是Plain風(fēng)格的,右邊的是Grouped風(fēng)格,這個區(qū)別還是很明顯的。
查看UITableView的幫助文檔我們會注意到UITableView有兩個Delegate分別為:dataSource和delegate。
dataSource是UITableViewDataSource類型,主要為UITableView提供顯示用的數(shù)據(jù)(UITableViewCell),指定UITableViewCell支持的編輯操作類型(insert,delete和reordering),并根據(jù)用戶的操作進(jìn)行相應(yīng)的數(shù)據(jù)更新操作,如果數(shù)據(jù)沒有更具操作進(jìn)行正確的更新,可能會導(dǎo)致顯示異常,甚至crush。
delegate是UITableViewDelegate類型,主要提供一些可選的方法,用來控制tableView的選擇、指定section的頭和尾的顯示以及協(xié)助完成cell的刪除和排序等功能。
提到UITableView,就必須的說一說NSIndexPath。UITableView聲明了一個NSIndexPath的類別,主要用來標(biāo)識當(dāng)前cell的在tableView中的位置,該類別有section和row兩個屬性,前者標(biāo)識當(dāng)前cell處于第幾個section中,后者代表在該section中的第幾行。
UITableView只能有一列數(shù)據(jù)(cell),且只支持縱向滑動,當(dāng)創(chuàng)建好的tablView第一次顯示的時候,我們需要調(diào)用其reloadData方法,強(qiáng)制刷新一次,從而使tableView的數(shù)據(jù)更新到最新狀態(tài)。
?
二、UITableViewController簡介
UITableViewController是系統(tǒng)提供的一個便利類,主要是為了方便我們使用UITableView,該類生成的時候就將自身設(shè)置成了其包含的tableView的dataSource和delegate,并創(chuàng)建了很多代理函數(shù)的框架,為我們大大的節(jié)省了時間,我們可以通過其tableView屬性獲取該controller內(nèi)部維護(hù)的tableView對象。默認(rèn)情況下使用UITableViewController創(chuàng)建的tableView是充滿全屏的,如果需要用到tableView是不充滿全屏的話,我們應(yīng)該使用UIViewController自己創(chuàng)建和維護(hù)tableView。
UITableViewController提供一個初始化函數(shù)initWithStyle:,根據(jù)需要我們可以創(chuàng)建Plain或者Grouped類型的tableView,當(dāng)我們使用其從UIViewController繼承來的init初始化函數(shù)的時候,默認(rèn)將會我們創(chuàng)建一個Plain類型的tableView。
UITableViewController默認(rèn)的會在viewWillAppear的時候,清空所有選中cell,我們可以通過設(shè)置self.clearsSelectionOnViewWillAppear = NO,來禁用該功能,并在viewDidAppear中調(diào)用UIScrollView的flashScrollIndicators方法讓滾動條閃動一次,從而提示用戶該控件是可以滑動的。
?
三、UITableViewCell介紹
UITableView中顯示的每一個單元都是一個UITableViewCell對象,看文檔的話我們會發(fā)現(xiàn)其初始化函數(shù)initWithStyle:reuseIdentifier:比較特別,跟我們平時看到的UIView的初始化函數(shù)不同。這個主要是為了效率考慮,因為在tableView快速滑動的滑動的過程中,頻繁的alloc對象是比較費(fèi)時的,于是引入了cell的重用機(jī)制,這個也是我們在dataSource中要重點(diǎn)注意的地方,用好重用機(jī)制會讓我們的tableView滑動起來更加流暢。
我們可以通過cell的selectionStyle屬性指定cell選中時的顯示風(fēng)格,以及通過accessoryType來指定cell右邊的顯示的內(nèi)容,或者直接指定accessoryView來定制右邊顯示的view。
系統(tǒng)提供的UITableView也包含了四種風(fēng)格的布局,分別是:
typedef enum {UITableViewCellStyleDefault,UITableViewCellStyleValue1,UITableViewCellStyleValue2,UITableViewCellStyleSubtitle } UITableViewCellStyle;這幾種文檔中都有詳細(xì)描述,這兒就不在累贅。然而可以想象系統(tǒng)提供的只是最常用的幾種類型,當(dāng)系統(tǒng)提供的風(fēng)格不符合我們需要的時候,我們就需要對cell進(jìn)行定制了,有以下兩種定制方式可選:
1、直接向cell的contentView上面添加subView
這是比較簡單的一種的,根據(jù)布局需要我們可以在不同的位置添加subView。但是此處需要注意:所有添加的subView都最好設(shè)置為不透明的,因為如果subView是半透明的話,view圖層的疊加將會花費(fèi)一定的時間,這會嚴(yán)重影響到效率。同時如果每個cell上面添加的subView個數(shù)過多的話(通常超過3,4個),效率也會受到比較大的影響。
下面我們看一個例子:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {NSArray *sections = [SvTableViewDataModal sections];SvSectionModal *sectionModal = [sections objectAtIndex:indexPath.section];static NSString *reuseIdetify = @"SvTableViewCell";UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdetify];if (!cell) {cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdetify];cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;cell.showsReorderControl = YES;for (int i = 0; i < 6; ++i) {UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100 + 15 * i, 0, 30, 20)];label.backgroundColor = [UIColor redColor];label.text = [NSString stringWithFormat:@"%d", i];[cell.contentView addSubview:label];[label release];}}cell.textLabel.backgroundColor = [UIColor clearColor];cell.textLabel.text = [sectionModal.cityNames objectAtIndex:indexPath.row];return cell; }在上面這個例子中,我往每個cell中添加了6個subView,而且每個subView都是半透明(UIView默認(rèn)是半透明的),這個時候滑動起來明顯就可以感覺到有點(diǎn)顫抖,不是很流暢。當(dāng)把每一個subView的opaque屬性設(shè)置成YES的時候,滑動會比之前流暢一些,不過還是有點(diǎn)兒卡。
2、從UITableViewCell派生一個類
通過從UITableViewCell中派生一個類,可以更深度的定制一個cell,可以指定cell在進(jìn)入edit模式的時候如何相應(yīng)等等。最簡單的實現(xiàn)方式就是將所有要繪制的內(nèi)容放到一個定制的subView中,并且重載該subView的drawRect方法直接把要顯示的內(nèi)容繪制出來(這樣可以避免subView過多導(dǎo)致的性能瓶頸),最后再將該subView添加到cell派生類中的contentView中即可。但是這樣定制的cell需要注意在數(shù)據(jù)改變的時候,通過手動調(diào)用該subView的setNeedDisplay方法來刷新界面,這個例子可以在蘋果的幫助文檔中的TableViewSuite工程中找到,這兒就不舉例了。
觀看這兩種定制cell的方法,我們會發(fā)現(xiàn)subView都是添加在cell的contentView上面的,而不是直接加到cell上面,這樣寫也是有原因的。下面我們看一下cell在正常狀態(tài)下和編輯狀態(tài)下的構(gòu)成圖:
cell在正常狀態(tài)下的構(gòu)成圖如下:
進(jìn)入編輯狀態(tài)下cell的構(gòu)成圖如下:
通過觀察上面兩幅圖片我們可以看出來,當(dāng)cell在進(jìn)入編輯狀態(tài)的時候,contentView會自動的縮放來給Editing control騰出位置。這也就是說如果我們把subView添加到contentView上,如果設(shè)置autoresizingMask為更具父view自動縮放的話,cell默認(rèn)的機(jī)制會幫我們處理進(jìn)入編輯狀態(tài)的情況。而且在tableView是Grouped樣式的時候,會為cell設(shè)置一個背景色,如果我們直接添加在cell上面的話,就需要自己考慮到這個背景色的顯示問題,如果添加到contentView上,則可以通過view的疊加幫助我們完成該任務(wù)。綜上,subView最好還是添加到cell的contentView中。
?
四、Reordering
為了使UITableVeiew進(jìn)入edit模式以后,如果該cell支持reordering的話,reordering控件就會臨時的把a(bǔ)ccessaryView覆蓋掉。為了顯示reordering控件,我們必須將cell的showsReorderControl屬性設(shè)置成YES,同時實現(xiàn)dataSource中的tableView:moveRowAtIndexPath:toIndexPath:方法。我們還可以同時通過實現(xiàn)dataSource中的 tableView:canMoveRowAtIndexPath:返回NO,來禁用某一些cell的reordering功能。
下面看蘋果官方的一個reordering流程圖:
上圖中當(dāng)tableView進(jìn)入到edit模式的時候,tableView會去對當(dāng)前可見的cell逐個調(diào)用dataSource的tableView:canMoveRowAtIndexPath:方法(此處官方給出的流程圖有點(diǎn)兒問題),決定當(dāng)前cell是否顯示reoedering控件,當(dāng)開始進(jìn)入拖動cell進(jìn)行拖動的時候,每滑動過一個cell的時候,會去掉用delegate的tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:方法,去判斷當(dāng)前劃過的cell位置是否可以被替換,如果不行則給出建議的位置。當(dāng)用戶放手時本次reordering操作結(jié)束,調(diào)用dataSource中的tableView:moveRowAtIndexPath:toIndexPath:方法更新tableView對應(yīng)的數(shù)據(jù)。
此處給個我寫demo中的更新數(shù)據(jù)的小例子:
// if you want show reordering control, you must implement moveRowAtndexPath, or the reordering control will not show // when use reordering end, this method is invoke - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {// update DataModalNSArray *sections = [SvTableViewDataModal sections];SvSectionModal *sourceSectionModal = [sections objectAtIndex:sourceIndexPath.section];NSString *city = [[sourceSectionModal.cityNames objectAtIndex:sourceIndexPath.row] retain];[sourceSectionModal.cityNames removeObject:city];[SvTableViewDataModal replaceSectionAtIndex:sourceIndexPath.section withSection:sourceSectionModal];SvSectionModal *desinationsSectionModal= [[SvTableViewDataModal sections] objectAtIndex:destinationIndexPath.section];[desinationsSectionModal.cityNames insertObject:city atIndex:destinationIndexPath.row];[SvTableViewDataModal replaceSectionAtIndex:destinationIndexPath.section withSection:desinationsSectionModal];[city release]; }上面代碼中首先拿到源cell所處的section,然后從該section對應(yīng)的數(shù)據(jù)中移除,然后拿到目標(biāo)section的數(shù)據(jù),然后將源cell的數(shù)據(jù)添加到目標(biāo)section中,并更新回數(shù)據(jù)模型,如果我們沒有正確更新數(shù)據(jù)模型的話,顯示的內(nèi)容將會出現(xiàn)異常。
?
五、Delete & Insert
cell的delete和insert操作大部分流程都是一樣的,當(dāng)進(jìn)入編輯模式的時候具體的顯示是delete還是insert取決與該cell的editingStyle的值,editStyle的定義如下:
typedef enum {UITableViewCellEditingStyleNone,UITableViewCellEditingStyleDelete,UITableViewCellEditingStyleInsert } UITableViewCellEditingStyle;當(dāng)tableView進(jìn)入編輯模式以后,cell上面顯示的delete還是insert除了跟cell的editStyle有關(guān),還與 tableView的delegate的tableView:editingStyleForRowAtIndexPath:方法的返回值有關(guān)(在這里嘮叨一句,其實delegate提供了很多改變cell屬性的機(jī)會,如非必要,還是不要去實現(xiàn)這些方法,因為執(zhí)行這些方法也造成一定的開銷)。
delete和insert的流程如下蘋果官方文檔中給出的圖所示:
下面是我寫的demo中刪除和添加部分的代碼:
#pragma mark - - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {NSLog(@"commit editStyle: %d", editingStyle);if (editingStyle == UITableViewCellEditingStyleDelete) {NSArray *sections = [SvTableViewDataModal sections];SvSectionModal *sourceSectionModal = [sections objectAtIndex:indexPath.section];[sourceSectionModal.cityNames removeObjectAtIndex:indexPath.row];[SvTableViewDataModal replaceSectionAtIndex:indexPath.section withSection:sourceSectionModal];[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationRight];}else {// do something for add itNSArray *sections = [SvTableViewDataModal sections];SvSectionModal *sourceSectionModal = [sections objectAtIndex:indexPath.section];[sourceSectionModal.cityNames insertObject:@"new City" atIndex:indexPath.row];[SvTableViewDataModal replaceSectionAtIndex:indexPath.section withSection:sourceSectionModal];[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationRight];} }代碼中首先判斷當(dāng)前操作是delete操作還是insert操作,相應(yīng)的更新數(shù)據(jù),最后根據(jù)情況調(diào)用tableView的insertRowsAtIndexPaths:withRowAnimation:或者deleteRowsAtIndexPaths:withRowAnimation:方法,對tableView的視圖進(jìn)行更新。cell的刪除和添加操作相對還是比較簡單的。
?
六、Cell的Select操作
當(dāng)我們在tableView中點(diǎn)擊一個cell的時候,將會調(diào)用tableView的delegate中的tableView:didSelectRowAtIndexPath:方法。
關(guān)于tableView的cell的選中,蘋果官方有以下幾個建議:
1、不要使用selection來表明cell的選擇狀態(tài),而應(yīng)該使用accessaryView中的checkMark或者自定義accessaryView來顯示選中狀態(tài)。
2、當(dāng)選中一個cell的時候,你應(yīng)該取消前一個cell的選中。
3、如果cell選中的時候,進(jìn)入下一級viewCOntroller,你應(yīng)該在該級菜單從navigationStack上彈出的時候,取消該cell的選中。
這塊兒再提一點(diǎn),當(dāng)一個cell的accessaryType為UITableViewCellAccessoryDisclosureIndicator的時候,點(diǎn)擊該accessary區(qū)域通常會將消息繼續(xù)向下傳遞,即跟點(diǎn)擊cell的其他區(qū)域一樣,將會掉delegate的tableView:didSelectRowAtIndexPath:方法,當(dāng)時如果accessaryView為 UITableViewCellAccessoryDetailDisclosureButton的時候,點(diǎn)擊accessaryView將會調(diào)用delegate的 tableView:accessoryButtonTappedForRowWithIndexPath:方法。
?
七、批量插入,刪除,部分更新操作
UITableView提供了一個批量操作的特性,這個功能在一次進(jìn)行多個row或者scetion的刪除,插入,獲取更新多個cell內(nèi)容的時候特別好用。所有的批量操作需要包含在beginUpdates和endUpdates塊中,否則會出現(xiàn)異常。
下面請看我demo中的一個批量操作的例子:
- (void)groupEdit:(UIBarButtonItem*)sender {[_tableView beginUpdates];// first update the data modal[_tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationTop];[_tableView deleteSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationTop];[SvTableViewDataModal deleteSectionAtIndex:0];SvSectionModal *section = [[SvTableViewDataModal sections] objectAtIndex:0];[section.cityNames insertObject:@"帝都" atIndex:0];[SvTableViewDataModal replaceSectionAtIndex:0 withSection:section];[_tableView endUpdates]; }上面的例子中我們可以看到先往tableView的第0個section的第0行添加一個cell,然后將第0個section刪掉。按照我們程序中寫的順序,那么新添加進(jìn)去的“帝都”,將不在會顯示,因為包含它的整個section都已經(jīng)被刪除了。
執(zhí)行程序前后結(jié)果如下圖:
demo中第0個section是陜西省的城市,第1個section是北京。左邊是執(zhí)行前的截圖,右邊是執(zhí)行后的截圖,觀察發(fā)現(xiàn)結(jié)果并不像我們前面推測的那樣。那是因為在批量操作時,不管代碼中先寫的添加操作還是刪除操作,添加操作都會被推出執(zhí)行,直到這個塊中所有的刪除操作都執(zhí)行完以后,才會執(zhí)行添加操作,這也就是上面蘋果官方圖片上要表達(dá)的意思。
蘋果官方文檔有一副圖可以幫助我們更好的理解這一點(diǎn):
原圖中操作是:首先刪除section 0中的row 1,然后刪除section 1,再向section 1中添加一行。執(zhí)行完批量更新以后就得到右半邊的結(jié)果。
八、IndexList
當(dāng)我們tableView中section有很多,數(shù)據(jù)量比較大的時候我們可以引入indexList,來方便完成section的定位,例如系統(tǒng)的通訊錄程序。我們可以通過設(shè)置tableView的sectionIndexMinimumDisplayRowCount屬性來指定當(dāng)tableView中多少行的時候開始顯示IndexList,默認(rèn)的設(shè)置是NSIntegerMax,即默認(rèn)是不顯示indexList的。
為了能夠使用indexlist我們還需要實現(xiàn)dataSource中一下兩個方法:
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView; - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index;第一個方法返回用于顯示在indexList中的內(nèi)容的數(shù)組,通常為A,B,C...Z。第二個方法的主要作用是根據(jù)用戶在indexList中點(diǎn)擊的位置,返回相應(yīng)的section的index值。這個例子可以在蘋果官方給出的TableViewSuite中找到,實現(xiàn)起來還是很簡單的。
?
九、其他
1、分割線
我們可以通過設(shè)置tableView的separatorStyle屬性來設(shè)置有無分割線以及分割線的風(fēng)格,其中style定義如下:
typedef enum {UITableViewCellSeparatorStyleNone,UITableViewCellSeparatorStyleSingleLine,UITableViewCellSeparatorStyleSingleLineEtched } UITableViewCellSeparatorStyle;同時還可以通過tableView的separatorColor屬性來設(shè)置分割線的顏色。
2、如何提高tableView的性能
a、重用cell
我們都知道申請內(nèi)存是需要時間,特別是在一段時間內(nèi)頻繁的申請內(nèi)存將會造成很大的開銷,而且上tebleView中cell大部分情況下布局都是一樣的,這個時候我們可以通過回收重用機(jī)制來提高性能。
b、避免content的重新布局
盡量避免在重用cell時候,對cell的重新布局,一般情況在在創(chuàng)建cell的時候就將cell布局好。
c、使用不透明的subView
在定制cell的時候,將要添加的subView設(shè)置成不透明的會大大減少多個view層疊加時渲染所需要的時間。
d、如果方便,直接重載subView的drawRect方法
如果定制cell的過程中需要多個小的元素的話,最好直接對要顯示的多個項目進(jìn)行繪制,而不是采用添加多個subView。
e、tableView的delegate的方法如非必要,盡量不要實現(xiàn)
tableView的delegate中的很多函數(shù)提供了對cell屬性的進(jìn)一步控制,比如每個cell的高度,cell是否可以編輯,支持的edit風(fēng)格等,如非必要最好不要實現(xiàn)這些方法因為快速的調(diào)用這些方法也會影響性能。
(以上5點(diǎn)建議,前三點(diǎn)來自蘋果官方文檔,后兩點(diǎn)我自己加的,有什么不對的地方,歡迎指正)
?
小結(jié):UITableView本身是很復(fù)雜的,本片博客只起到拋磚引玉的作用,歡迎大家補(bǔ)充。想用好UITableView,還是需要實際項目中的鍛煉的。
?
注:1、博客中的圖片出了程序運(yùn)行截圖,其他的都來自蘋果官方文檔。
2、歡迎轉(zhuǎn)載,轉(zhuǎn)載請在顯著位置添加原文鏈接地址和作者名稱,謝謝!
?
轉(zhuǎn)載于:https://www.cnblogs.com/apem/p/3967063.html
總結(jié)
以上是生活随笔為你收集整理的UITableView 学习笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Putty的设置保存
- 下一篇: css进阶读书笔记