以下內容為原創,歡迎轉載,轉載請注明
來自天天博客:http://www.cnblogs.com/tiantianbyconan/p/3403124.html
本人從在學校開始到現在上班(13年畢業)一直做web和android方面的開發,最近才開學習及ios的開發,所以ios學習中有不當之處,請大家留言賜教啦
以前從來沒有接觸過Objective-C這門語言,不過我想面向對象編程應該大體思想都差不多
在ios中的UITableView學習中,開發過android的朋友應該馬上會聯想到ListView和GridView這兩個控件,接下來以ListView為例子,跟UITableView做個對比,看看它們實現的方式有什么相同之處。怎么樣能讓有android開發經驗的朋友,馬上掌握UITableView這個控件
先新建一個demo,取名TabViewTest(原諒我吧- -,本來要取名TableViewTest,誰知腦抽新建項目的時候寫錯了,誒。。。算了,將錯就錯吧- -)

ios沒有命名空間的概念,沒有包概念(這也是為什么ios中的類都有前綴的原因,比如NS等),所以上面像“包”一樣的文件夾都是我自己新建的“group”,只是為了看起來比較有分層的概念而已,打開finder,到項目文件里一看如下圖,媽呀- -,所有的類都擠在一個文件夾里面。。。這是我覺得蛋疼的地方之一-。-

再回來看看我們項目結構,我分的幾個group,如果我把controller這個group的名字改成“activity”,android開發者肯定有種似曾相識的感覺了:
controller:控制層group,相當於android中的activity
layout:布局group,相當於android中res目錄下的layout(xml布局文件)
model:這個不用說就知道放了什么東西了,經典的Person這個測試用的數據結構
adapter:為了還念android中的適配器,然后我就取了這么個group名字
好了,現在正式開始代碼的編寫
打開MainAppDelegate.m,它實現了UIApplicationDelegate協議,所以可以在該類中實現應用狀態的回調函數
在application:didFinishLaunchingWithOptions:方法(應用程序啟動時回調該方法)中來設置它的RootController(根控制器,不知道這樣翻譯專不專業- -),我的實現是生成一個UINavigationController作為它的root controller,然后把自己新建的一個NaviRootController(在這個Controller中放入一個UITableView,具體下面會講到)作為UINavigationController的root view,具體代碼如下(這個不是我們本次的重點,所以一筆帶過):
1 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 2 { 3 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 4 5 // 生成UINavigationController對象,並設置它的RootController 6 UINavigationController *naviController = [[UINavigationController alloc] initWithRootViewController:[[NaviRootController alloc] init]]; 7 // 然后把NavigationController設置為window的RootViewController 8 [self.window setRootViewController:naviController]; 9 10 self.window.backgroundColor = [UIColor whiteColor]; 11 [self.window makeKeyAndVisible]; 12 return YES; 13 }
然后,重點就是NaviRootController這個Controller了,
新建NaviRootController,繼承UIViewController,在.h文件中:
聲明一個NSMutableArray對象data作為tableView的數據源(相當於在android中經常用到的ArrayList<Person> data。NSMutableArray是數組,ArrayList在java中的底層實現本來用的就是數組,所以很好理解)
聲明一個用於適配和綁定tableView數據的適配器TableViewAdapter *adapter(這個類是我自己寫的,下面會講到。java中要實現ListView中的數據綁定適配,也要通過一個繼承於BaseAdapter的適配器進行數據的適配吧,也很好理解)
聲明一個UITableView對象(相當於android中,在activity中聲明一個private ListView listView;)
具體代碼如下:
1 // 2 // NaviRootController.h 3 // TabViewTest 4 // 5 // Created by WANGJIE on 13-10-31. 6 // Copyright (c) 2013年 WANGJIE. All rights reserved. 7 // 8 9 #import <UIKit/UIKit.h> 10 @class TableViewAdapter; 11 @interface NaviRootController : UIViewController 12 { 13 NSMutableArray *data; 14 TableViewAdapter *adapter; 15 } 16 17 @property (weak, nonatomic) IBOutlet UITableView *tableView; 18 19 20 @property NSMutableArray *data; 21 @property(strong, nonatomic) TableViewAdapter *adapter; 22 @end
好了,聲明部分到此為止,接下來看看實現部分
剛剛在MainAppDelegate中,生成了一個NaviRootController對象,然后把這個對象加入到了UINavigationController對象的rootViewController。生成NaviRootController對象的時候,調用了init方法,我們應該在init方法里面去初始化布局,如下:
1 - (id)init 2 { 3 self = [self initWithNibName:@"navi_root" bundle:nil]; 4 return self; 5 } 6 7 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 8 { 9 self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 10 if (self) { 11 // 獲得當前導航欄對象 12 UINavigationItem *item = [self navigationItem]; 13 // 設置導航欄左按鈕 14 UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithTitle:@"leftButton" style:UIBarButtonItemStylePlain target:self action:@selector(buttonClickedAction:)]; 15 [leftButton setTag:0]; 16 [item setLeftBarButtonItem:leftButton animated:YES]; 17 // 設置導航欄右按鈕 18 UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithTitle:@"rightButton" style:UIBarButtonItemStyleDone target:self action:@selector(buttonClickedAction:)]; 19 [rightButton setTag:1]; 20 [item setRightBarButtonItem:rightButton animated:YES]; 21 22 } 23 return self; 24 }
我們在init方法中調用了initWithNibName:bundle:方法,傳入了NibName這個字符串,這個是什么?Nib是ios里面的布局文件,也就是相當於android中類似main.xml這種布局文件吧,現在傳進去的時nibname,也就是布局文件的名稱。所以,沒錯,傳入這個參數后,當前的controller(也就是NaviRootController就跟傳入的這個布局文件綁定起來了,類似於android中activity中熟悉的setContentView(R.layout.main)一樣!)
然后我們順便看看navi_root.xib這個布局文件:

整個界面就一個UITableView,對了,別忘了在File's Owner中把custom class改成NaviRootController。鍵盤按住control,用鼠標拖動左邊欄的“Table View”到NaviRootController.h中,會自動生成UITableView聲明,並自動綁定。
接下來回到NaviRootController的初始化方法中。
initWithNibName:bundle:方法中后面的UINavigationItem相關的代碼是用來設置導航欄左邊和右邊的按鈕,既然是個demo,所以也沒什么特別的功能,就是點擊下,跳出一個UIAlertView提示下,所以一筆帶過。
此時界面布局和controller已經綁定起來了,現在的任務應該是初始化UITableView的數據,也就是上面的data屬性,但是在哪里初始化比較好呢?
剛開始,我是直接在init方法中直接去初始化數據,但是失敗了,不管在init方法中初始化多少次(data調用多少次addObject方法),data的值永遠都是nil(相當於在android中,不管調用多少次list.add(...)方法,list中一條數據也沒有加入!),猜測是因為在init方法中的時候屬性和控件都還沒有被初始化。
最后我的解決辦法就是在viewDidLoad方法中去加載數據。viewDidLoad這個回調方法是會在控件加載完畢后調用,所以,我認為在這里去加載數據和做控件的初始化操作是比較合理的。
viewDidLoad方法實現如下:
1 - (void)viewDidLoad 2 { 3 4 // Do any additional setup after loading the view. 5 [super viewDidLoad]; 6 7 if (!adapter) { 8 [self initData]; 9 10 // 生成適配器委托對象 11 adapter = [[TableViewAdapter alloc] initWithSource:data Controller:self]; 12 13 // 設置適配器委托對象 14 [[self tableView] setDelegate:adapter]; 15 [[self tableView] setDataSource:adapter]; 16 17 } 18 }
如上,在viewDidLoad中,我先去初始化數據(demo中的實現其實就是循環了14次,往data中加了14個Person對象),然后生成一個適配器委托對象,傳入data(數據源)和self(當前controller對象),相當於android中的 adapter = new MyAdapter(list, this);有木有??!!
然后,setDelegate用來設置委托對象(相當於android中的listView.setAdapter(adapter)),setDataSource用來設置數據源。
這里,完整地列出NaviRootController的代碼:
1 // 2 // NaviRootController.m 3 // TabViewTest 4 // 5 // Created by WANGJIE on 13-10-31. 6 // Copyright (c) 2013年 WANGJIE. All rights reserved. 7 // 8 9 #import "NaviRootController.h" 10 #import "Person.h" 11 #import "TableViewAdapter.h" 12 13 @interface NaviRootController () 14 15 @end 16 17 @implementation NaviRootController 18 @synthesize data, adapter; 19 20 - (id)init 21 { 22 self = [self initWithNibName:@"navi_root" bundle:nil]; 23 return self; 24 } 25 26 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 27 { 28 self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 29 if (self) { 30 // Custom initialization 31 // 獲得當前導航欄對象 32 UINavigationItem *item = [self navigationItem]; 33 // 設置導航欄左按鈕 34 UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithTitle:@"leftButton" style:UIBarButtonItemStylePlain target:self action:@selector(buttonClickedAction:)]; 35 [leftButton setTag:0]; 36 [item setLeftBarButtonItem:leftButton animated:YES]; 37 // 設置導航欄右按鈕 38 UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithTitle:@"rightButton" style:UIBarButtonItemStyleDone target:self action:@selector(buttonClickedAction:)]; 39 [rightButton setTag:1]; 40 [item setRightBarButtonItem:rightButton animated:YES]; 41 42 43 44 45 } 46 return self; 47 } 48 49 /** 50 * 初始化列表數據 51 */ 52 - (void)initData 53 { 54 data = [[NSMutableArray alloc] init]; 55 NSLog(@"%@", NSStringFromSelector(_cmd)); 56 Person *person; 57 for (int i = 0; i < 14; i++) { 58 person = [[Person alloc] init]; 59 [person setName:[@"name" stringByAppendingString: 60 [NSString stringWithFormat:@"%d", i] 61 ] 62 ]; 63 64 [person setAge:i + 10]; 65 66 [person setPic:[UIImage imageNamed:@"Hypno.png"]]; 67 68 [data addObject:person]; 69 70 } 71 } 72 73 74 - (void)viewDidLoad 75 { 76 77 // Do any additional setup after loading the view. 78 [super viewDidLoad]; 79 80 if (!adapter) { 81 [self initData]; 82 83 // 生成適配器委托對象 84 adapter = [[TableViewAdapter alloc] initWithSource:data Controller:self]; 85 86 // 設置適配器委托對象 87 [[self tableView] setDelegate:adapter]; 88 [[self tableView] setDataSource:adapter]; 89 90 } 91 92 93 94 } 95 96 - (void)viewWillAppear:(BOOL)animated 97 { 98 [super viewWillAppear:YES]; 99 100 } 101 102 - (void)didReceiveMemoryWarning 103 { 104 [super didReceiveMemoryWarning]; 105 // Dispose of any resources that can be recreated. 106 } 107 108 // 按鈕點擊事件回調 109 - (void)buttonClickedAction:(id)sender 110 { 111 112 NSString *message; 113 switch ([sender tag]) { 114 case 0: 115 message = @"left button clicked!"; 116 break; 117 case 1: 118 message = @"right button clicked!"; 119 break; 120 default: 121 message = @"unknow button clicked!"; 122 break; 123 } 124 125 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"alert view" message:message delegate:self cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil]; 126 [alert show]; 127 } 128 129 130 131 132 @end
好了,UITableView的初始化准備工作到此就做完了,現在干嘛?
當然去編寫TableViewAdapter這個類啊。數據源有了(並且初始化完畢),用以顯示的控件(UITableView)有了(並且初始化完畢),而且兩個還已經綁定起來了。但是還缺的就是一個角色,這個角色可以把數據源中的數據跟控件中某個相應的子控件適配起來。比如數據源中有4個數據,A、B、C、D,控件有one、two、three、four這4個控件,這個角色的任務就是告訴one:你要顯示的數據是A;告訴two:你要顯示的數據是B;告訴three:你要顯示的數據是C;告訴four:你要顯示的數據是D!
這個角色就是適配器!也就是下面要說的那個TableViewAdapter
新建TableViewAdapter,實現<UITableViewDataSource, UITableViewDelegate>這兩個協議(android中適配器不同的是要繼承BaseAdapter類)。聲明一個數據源data,這個數據源是從NaviRootController中傳過來的已經初始化好了的數據源,還有一個聲明是NaviRootController對象傳過來的self(需要什么,就讓調用者傳什么過來,這個應該都懂的-。-),另外還有一個自己寫的初始化方法(自己寫初始化方法init打頭的方法就行,不像java中的構造方法,方法名要跟類名相同,不過這些都是換湯不換葯)
然后看看TableViewAdapter的實現類(.m)
實現了這兩個協議后,你就能覆寫里面的一些UITableView的回調方法了,比如:
tableView:numberOfRowsInSection:方法,返回數據源的數量就行了(類似android的adapter中自己要實現的getCount()方法!)
tableView:cellForRowAtIndexPath:這個是方法是這里最關鍵的一個方法了,就是在這里進行數據的適配工作(類似android的adapter中自己要實現的getView()方法!),這里返回的UITableViewCell就是類表中的一項(類似android中listview的一個item),這個一項的布局,已經在table_cell.xib文件中布局完畢,如下:

設置File's Owner為TableViewAdapter,設置Table View Cell的identifier設置為“MyTableCell”,這個可以任意取名,但是要跟后面的方法實現中統一(跟哪里統一?作用是神馬?這些下面會講到,別急-。-),接着設置ImageView的tag為1,nameLabel的tag為2,ageLabel的tag為3(tag的作用,下面也會講到)。
接着,回到tableView:cellForRowAtIndexPath:這個方法,它的實現如下所示:
1 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 2 { 3 Person *p = [data objectAtIndex:[indexPath row]]; 4 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyTableCell"]; 5 if (!cell) { 6 NSArray *nibViews = [[NSBundle mainBundle] loadNibNamed:@"table_cell" owner:self options:nil]; 7 cell = [nibViews objectAtIndex:0]; 8 } 9 10 UIImageView *picIv = (UIImageView *)[cell viewWithTag:1]; 11 UILabel *nameLb = (UILabel *)[cell viewWithTag:2]; 12 UILabel *ageLb = (UILabel *)[cell viewWithTag:3]; 13 14 [nameLb setText:[p name]]; 15 [ageLb setText:[NSString stringWithFormat:@"%d", [p age]]]; 16 [picIv setImage:[p pic]]; 17 18 return cell; 19 }
如上圖所示:
第3行,是用於獲得所要顯示的數據Person對象(這里的[indexPath row]相當於android的adapter的getView方法的position參數。indexPath中有兩個參數,row和section,表示行和列,因為我們現在是要顯示一個列表,所以只需要row這個參數就可以了)
第4行,是用於資源的重復利用,根據標示符“MyTableCell”去獲得一個可再利用的UITableViewCell(這里的標示符就要跟前面在xib文件中設置的標示符要一致,這樣才能被識別到,然后在這里被獲取到),如果沒有獲得到,就去新創建一個UITableViewCell。
第6、7行,是創建新的UITableViewCell的代碼,通過mianBundle的loadNibNamed:owner:options方法根據xib的名字去創建(跟android中的LayoutInflater.inflate()方法通過R.layout.xxx的方法創建類似),loadNibNamed:owner:options返回的是一個數組,得到第一個就是UITableViewCell了。
第10、11、12行,是獲取到或者新建的cell通過控件之前設置的tag來獲得相應地子控件(現在知道之前為什么要設置xib文件中控件的tag了吧?這個跟android中的findViewByTag/findViewById又是很類似的!)
第14、15、16行,是為剛剛獲得的cell中的子控件適配數據,讓它可以把數據顯示出來。
第18行,把生成數據適配完畢的UITableViewCell返回出去(這跟android中的也是很類似)
TableViewAdapter代碼如下:
1 // 2 // TableViewAdapter.m 3 // TabViewTest 4 // 5 // Created by WANGJIE on 13-10-31. 6 // Copyright (c) 2013年 WANGJIE. All rights reserved. 7 // 8 9 #import "TableViewAdapter.h" 10 #import "Person.h" 11 #import "NaviRootController.h" 12 13 @implementation TableViewAdapter 14 15 - (id)initWithSource:(NSMutableArray *)source Controller:(NaviRootController *)context 16 { 17 NSLog(@"initWithSource..."); 18 self = [super init]; 19 if (self) { 20 data = source; 21 controller = context; 22 } 23 return self; 24 } 25 26 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 27 { 28 29 return [data count]; 30 } 31 32 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 33 { 34 NSLog(@"%@", NSStringFromSelector(_cmd)); 35 // UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"]; 36 // if (!cell) { 37 // cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"UITableViewCell"]; 38 // } 39 // 獲得當前要顯示的數據 40 Person *p = [data objectAtIndex:[indexPath row]]; 41 // 42 // [[cell textLabel] setText:[p name]]; 43 // 記得在cell.xib文件中設置identifier以達到重用的目的 44 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyTableCell"]; 45 if (!cell) { 46 // 通過mainBundle的loadNibNamed方法去加載布局,類似android中的LayoutInflater.inflate()方法 47 NSArray *nibViews = [[NSBundle mainBundle] loadNibNamed:@"table_cell" owner:self options:nil]; 48 cell = [nibViews objectAtIndex:0]; 49 // cell.selectionStyle = UITableViewCellSelectionStyleNone; 50 } 51 // 通過在cell.xib中各控件設置的tag來獲得控件,類似android中的findViewByTag/findViewById 52 UIImageView *picIv = (UIImageView *)[cell viewWithTag:1]; 53 UILabel *nameLb = (UILabel *)[cell viewWithTag:2]; 54 UILabel *ageLb = (UILabel *)[cell viewWithTag:3]; 55 56 [nameLb setText:[p name]]; 57 [ageLb setText:[NSString stringWithFormat:@"%d", [p age]]]; 58 [picIv setImage:[p pic]]; 59 60 61 return cell; 62 } 63 64 65 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 66 { 67 Person *person = [data objectAtIndex:[indexPath row]]; 68 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"cell clicked!" message:[NSString stringWithFormat:@"%@ clicked!", [person name]]delegate:self cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil]; 69 [alert show]; 70 71 } 72 73 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 74 { 75 return [self tableView:tableView cellForRowAtIndexPath:indexPath].frame.size.height; 76 } 77 78 79 @end
好了!到此為止整個UITableView實現的流程基本完成了,可以看出跟android的ListView和GridView的實現流程很相似,理解了其中一個,另一個也能很好的理解它的工作流程。
最后來一張效果圖:

