iOS開發 - 最常用控制項 UITableView詳解
UITableView掌握點
設定UITableView的dataSource、delegate
UITableView多組資料和單組資料的展示
UITableViewCell的常見屬性
UITableView的效能最佳化(cell的迴圈利用)
自訂Cell
如何展示資料
UITableView需要一個資料來源(dataSource)來顯示資料
UITableView會向資料來源查詢一共有多少行資料以及每一行顯示什麼資料等
沒有設定資料來源的UITableView只是個空殼
凡是遵守UITableViewDataSource協議的OC對象,都可以是UITableView的資料來源
UITableViewUITableViewDataSource
@property (nonatomic, assign) id dataSource;
//調用資料來源的下面方法得知一共有多少組資料- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;//調用資料來源的下面方法得知每一組有多少行資料- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;//調用資料來源的下面方法得知每一行顯示什麼內容- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
MVC設計思想
MVC是一種設計思想,貫穿於整個iOS開發中,需要積累一定的項目經驗,才能深刻體會其中的含義和好處
MVC中的三個角色
//M:Model,模型資料//V:View,視圖(介面)//C:Control,控制中心
MVC的幾個明顯的特徵和體現:
View上面顯示什麼東西,取決於Model
只要Model資料改了,View的顯示狀態會跟著更改
Control負責初始化Model,並將Model傳遞給View去解析展示
UITableViewCell簡介
UITableView的每一行都是一個UITableViewCell,通過dataSource的tableView:cellForRowAtIndexPath:方法來初始化每一行
UITableViewCell內部有個預設的子視圖:contentView,contentView是UITableViewCell所顯示內容的父視圖,可顯示一些輔助指示視圖
輔助指示視圖的作用是顯示一個表示動作的表徵圖,可以通過設定UITableViewCell的accessoryType來顯示,預設是UITableViewCellAccessoryNone(不顯示輔助指示視圖),其他值如下:
UITableViewCellAccessoryDisclosureIndicatorUITableViewCellAccessoryDetailDisclosureButtonUITableViewCellAccessoryCheckmark
還可以通過cell的accessoryView屬性來自訂輔助指示視圖(比如往右邊放一個開關)
UITableViewCell的contentView
contentView下預設有3個子視圖
其中2個是UILabel(通過UITableViewCell的textLabel和detailTextLabel屬性訪問)
第3個是UIImageView(通過UITableViewCell的imageView屬性訪問)
UITableViewCell還有一個UITableViewCellStyle屬性,用於決定使用contentView的哪些子視圖,以及這些子視圖在contentView中的位置
UITableViewCell結構
Cell的重用原理
iOS裝置的記憶體有限,如果用UITableView顯示成千上萬條資料,就需要成千上萬個UITableViewCell對象的話,那將會耗盡iOS裝置的記憶體。要解決該問題,需要重用UITableViewCell對象
重用原理:當滾動列表時,部分UITableViewCell會移出視窗,UITableView會將視窗外的UITableViewCell放入一個對象池中,等待重用。當UITableView要求dataSource返回UITableViewCell時,dataSource會先查看這個對象池,如果池中有未使用的UITableViewCell,dataSource會用新的資料配置這個UITableViewCell,然後返回給UITableView,重新顯示到視窗中,從而避免建立新對象
還有一個非常重要的問題:有時候需要自訂UITableViewCell(用一個子類繼承UITableViewCell),而且每一行用的不一定是同一種UITableViewCell,所以一個UITableView可能擁有不同類型的UITableViewCell,對象池中也會有很多不同類型的UITableViewCell,那麼UITableView在重用UITableViewCell時可能會得到錯誤類型的UITableViewCell
解決方案:UITableViewCell有個NSString *reuseIdentifier屬性,可以在初始化UITableViewCell的時候傳入一個特定的字串標識來設定reuseIdentifier(一般用UITableViewCell的類名)。當UITableView要求dataSource返回UITableViewCell時,先通過一個字串標識到對象池中尋找對應類型的UITableViewCell對象,如果有,就重用,如果沒有,就傳入這個字串標識來初始化一個UITableViewCell對象
Cell的重用代碼
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ // 1.定義一個cell的標識 static NSString *ID = @"mjcell"; // 2.從緩衝池中取出cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; // 3.如果緩衝池中沒有cell if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; } // 4.設定cell的屬性... return cell;}
使用xib封裝一個view的步驟建立一個xib檔案描述一個view的內部結構(假設叫做XXXCell.xib) 建立一個自訂的類 (自訂類需要繼承自系統內建的view, 繼承自哪個類, 取決於xib根對象的Class) 建立類的類名最好跟xib的檔案名稱保持一致(比如類名就叫做XXXTgCell) 將xib中的控制項 和 自訂類的.m檔案 進行連線 提供一個類方法返回一個建立好的自訂view(屏蔽從xib載入的過程) 提供一個模型屬性讓外界傳遞模型資料 重寫模型屬性的setter方法,在這裡將模型資料展示到對應的子控制項上面Delegate的使用場合
對象A內部發生了一些事情,想通知對象B對象B想監聽對象A內部發生了什麼事情對象A想在自己的方法內部調用對象B的某個方法,並且對象A不能對對象B有耦合依賴對象A想傳遞資料給對象B……以上情況,結果都一樣:對象B是對象A的代理(delegate)
先搞清楚誰是誰的代理(delegate)
定義代理協議,協議名稱的命名規範:控制項類名 + Delegate
定義代理方法
代理方法一般都定義為@optional
代理方法名都以控制項名開頭
代理方法至少有1個參數,將控制項本身傳遞出去
設定代理(delegate)對象 (比如myView.delegate = xxxx;)
代理對象遵守協議
代理對象實現協議裡面該實現的方法
在恰當的時刻調用代理對象(delegate)的代理方法,通知代理髮生了什麼事情
(在調用之前判斷代理是否實現了該代理方法)
通過代碼自訂cell(cell的高度不一致)
1.建立一個繼承自UITableViewCell的類
2.重寫initWithStyle:reuseIdentifier:方法
添加所有需要顯示的子控制項(不需要設定子控制項的資料和frame, 子控制項要添加到contentView中)
進行子控制項一次性的屬性設定(有些屬性只需要設定一次, 比如字型\固定的圖片)
3.提供2個模型
資料模型: 存放文字資料\圖片資料
frame模型: 存放資料模型\所有子控制項的frame\cell的高度
4.cell擁有一個frame模型(不要直接擁有資料模型)
5.重寫frame模型屬性的setter方法: 在這個方法中設定子控制項的顯示資料和frame
6.frame模型資料的初始化已經採取懶載入的方式(每一個cell對應的frame模型資料只載入一次)
UITableView執行個體一: 城市列表的展示
資料來源plist檔案:
#import "ViewController.h"
@interface ViewController ()@property (weak, nonatomic) IBOutlet UITableView *tableView;@property (weak, nonatomic) IBOutlet UIToolbar *toolBar;@property NSArray *provinces;@property NSArray *cities;@end
@implementation ViewController- (void)viewDidLoad{ [super viewDidLoad]; // 載入資料 NSBundle *mainbundle = [NSBundle mainBundle]; //如果類型不給,需要在resource參數中給出尾碼名 self.provinces = [NSArray arrayWithContentsOfFile:[mainbundle pathForResource:@"provinces.plist" ofType:nil]]; self.cities =[NSArray arrayWithContentsOfFile:[mainbundle pathForResource:@"cities.plist" ofType:nil]]; //處理toolBar的按鈕事件綁定 NSArray *items=self.toolBar.items; for (UIBarButtonItem *item in items) { if (item.title) { NSLog(@"當前的item: %@",item.title); [item setAction:@selector(toolBarItemClick:)]; } }}#pragma mark - UITableViewDataSouce 資料來源方法/** * Section的個數,一共有多少組資料 */-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView{ return self.provinces.count;}/** * 第section組有多少行 */-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return [self.cities[section] count];}/** * 每一行顯示的內容(cell) */-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ NSLog(@"TableView來擷取內容:section:%d,row:%d",indexPath.section,indexPath.row); UITableViewCell *cell =[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; //取cities中的城市數組 NSArray *city = self.cities[indexPath.section]; cell.textLabel.text =city[indexPath.row]; [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; //如果當前的cell是被選中的,則設定其選中的accessoryType NSArray *selpaths =[tableView indexPathsForSelectedRows]; if ([selpaths containsObject:indexPath]) { cell.accessoryType = UITableViewCellAccessoryCheckmark; } return cell;}/** * 添加索引 */- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{ return self.provinces;}/** * 顯示第section組的頭部標題 */-(NSString*) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{ return self.provinces[section];//每個省的名稱}#pragma mark - UITableViewDelegate 代理方法/** * 即將被選中時調用 */-(NSIndexPath *) tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath{ NSLog(@"section:%d,row:%d即將被選中",indexPath.section,indexPath.row); return indexPath;}/** * 當前行已經被選中時調用 */-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ NSLog(@"section:%d,row:%d已經被選中",indexPath.section,indexPath.row); //設定其accessoryType 為CheckMark UITableViewCell *cell=[tableView cellForRowAtIndexPath:indexPath]; [cell setAccessoryType:UITableViewCellAccessoryCheckmark];}/** * 當前行即將被取消選中時調用 */-(NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath{ NSLog(@"section:%d,row:%d即將被取消被選中",indexPath.section,indexPath.row); return indexPath;}/** * 當前行被取消選中時調用 */-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{ UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; [cell setAccessoryType:UITableViewCellAccessoryNone]; NSLog(@"section:%d,row:%d已經被取消被選中",indexPath.section,indexPath.row);}#pragma mark - 事件方法/** * 點擊按鈕 */- (void)toolBarItemClick:(UIBarButtonItem *)sender{ NSLog(@"按鈕: %@被點擊了",sender.title); if (sender.tag==3) { NSArray * selIndexLst = [self.tableView indexPathsForSelectedRows]; NSLog(@"selIndex:%@",selIndexLst); return; }}@end
:
UITableView執行個體二: 單組資料模型展示
資料來源plist檔案:
//模型類#import @interface Hero : NSObject@property(nonatomic,copy) NSString * name;@property(nonatomic,copy) NSString * icon;@property(nonatomic,copy) NSString * intro;+ (instancetype)heroWithDict:(NSDictionary *)dict;- (instancetype)initWithDict:(NSDictionary *)dict;@end
#import "Hero.h"@implementation Hero+ (instancetype)heroWithDict:(NSDictionary *)dict{ return [[self alloc]initWithDict:dict];}- (instancetype)initWithDict:(NSDictionary *)dict{ if (self==[super init]) { [self setValuesForKeysWithDictionary:dict]; } return self;}@end
#import "ViewController.h"#import "Hero.h"@interface ViewController ()@property(nonatomic,strong) NSArray* heros;@property (weak, nonatomic) IBOutlet UITableView *tableView;@end
@implementation ViewController- (void)viewDidLoad { [super viewDidLoad];}/** * 隱藏標題列 */- (BOOL)prefersStatusBarHidden{ return YES;}//初始化- (NSArray *)heros{ if (_heros==nil) { //1.獲得plist的全路徑 NSString * path=[[NSBundle mainBundle]pathForResource:@"heros.plist" ofType:nil]; //2.載入數組 NSArray * dictArray=[NSArray arrayWithContentsOfFile:path]; //3.將dictArray裡面的所有字典轉成模型對象,放到新的數組中 NSMutableArray *heroArray=[NSMutableArray array]; for (NSDictionary *dict in dictArray) { //3.1建立模型對象 Hero *hero=[Hero heroWithDict:dict]; //3.2添加模型對象到數組中 [heroArray addObject:hero]; } //4.賦值 _heros=heroArray; } return _heros;}#pragma mark - 資料來源方法- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return self.heros.count;}/** * 知識點一: cell的效能最佳化 * 1.通過一個標識去緩衝池中尋找可迴圈利用的cell * 2.如果緩衝池找不到可迴圈利用的cell,就會建立一個新的cell,給cell貼個標識 * 3.給cell設定新的資料 *//** * 每當有一個cell進入視野範圍內,就會調用 */- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ //static修飾局部變數:可以保證局部變數只分配一次儲存空間(只初始化一次) static NSString * ID=@"hero"; //1.通過一個標識去緩衝池中尋找可迴圈利用的cell,dequeue:出列(尋找) UITableViewCell * cell=[tableView dequeueReusableCellWithIdentifier:ID]; //2.如果沒有可迴圈利用cell if (cell==nil) { cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; NSLog(@"------緩衝池中找不到,所以建立了cell- %ld",(long)indexPath.row); } //3.取出模型 Hero *hero=self.heros[indexPath.row]; //設定cell的資料 cell.textLabel.text=hero.name; cell.detailTextLabel.text=hero.intro; cell.imageView.image=[UIImage imageNamed:hero.icon]; //設定cell右邊指標的類型 cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator; //設定圖片背景 UIImageView *bgView=[[UIImageView alloc]init]; bgView.image=[UIImage imageNamed:@"buttondelete"]; cell.backgroundView=bgView; //設定選中背景 UIView *selectedbgView=[[UIView alloc]init]; selectedbgView.backgroundColor=[UIColor greenColor]; cell.selectedBackgroundView=selectedbgView; return cell;}#pragma mark - 代理方法//控制與狀態列之間的高度- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ return 60;}- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ //1.取得被點擊這行對應的模型 Hero *hero=self.heros[indexPath.row]; //彈框 UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"資料展示" message:nil delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"確定", nil]; //設定對話方塊的類型 alert.alertViewStyle=UIAlertViewStylePlainTextInput; //取得唯一的那個文字框,顯示英雄的名稱 [alert textFieldAtIndex:0].text=hero.name; [alert show]; //綁定行號到alertView上 alert.tag=indexPath.row;}//- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath//{// // Deselect : 取消選中// NSLog(@"取消選中了第%d行", indexPath.row);//}#pragma mark - alertView的代理方法- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ if (buttonIndex==0) return; //1.取得文字框最後的文字 NSString *name=[alertView textFieldAtIndex:0].text; //2.修改模型屬性 int row=alertView.tag; Hero *hero=self.heros[row]; hero.name=name; /** * 知識點二: 資料的重新整理 * reloadData:tableView會向資料來源重新請求資料,重新調用資料來源的相應方法取得資料 * 重新調用資料來源的tableView:numberOfRowsInSection:獲得行數 * 重新調用資料來源的tableView:cellForRowAtIndexPath:得知每一行顯示怎樣的cell */ //3.讓tableView重新載入模型資料 //全部重新整理 //[self.tableView reloadData]; //局部重新整理 NSIndexPath *path=[NSIndexPath indexPathForItem:row inSection:0]; [self.tableView reloadRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationBottom];}@end
: