【IOS】从android角度来实现(理解)IOS的UITableView
以下内容为原创,欢迎转载,转载请注明
来自天天博客: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,具体代码如下(这个不是我们本次的重点,所以一笔带过):
- - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
- // 生成UINavigationController对象,并设置它的RootController
- UINavigationController *naviController = [[UINavigationController alloc] initWithRootViewController:[[NaviRootController alloc] init]];
- // 然后把NavigationController设置为window的RootViewController
- [self.window setRootViewController:naviController];
- self.window.backgroundColor = [UIColor whiteColor];
- [self.window makeKeyAndVisible];
- return YES;
- }
然后,重点就是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;)
具体代码如下:
- //
- // NaviRootController.h
- // TabViewTest
- //
- // Created by WANGJIE on 13-10-31.
- // Copyright (c) 2013年 WANGJIE. All rights reserved.
- //
- #import <UIKit/UIKit.h>
- @class TableViewAdapter;
- @interface NaviRootController : UIViewController
- {
- NSMutableArray *data;
- TableViewAdapter *adapter;
- }
- @property (weak, nonatomic) IBOutlet UITableView *tableView;
- @property NSMutableArray *data;
- @property(strong, nonatomic) TableViewAdapter *adapter;
- @end
好了,声明部分到此为止,接下来看看实现部分
刚刚在MainAppDelegate中,生成了一个NaviRootController对象,然后把这个对象加入到了UINavigationController对象的rootViewController。生成NaviRootController对象的时候,调用了init方法,我们应该在init方法里面去初始化布局,如下:
- - (id)init
- {
- self = [self initWithNibName:@"navi_root" bundle:nil];
- return self;
- }
- - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
- {
- self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
- if (self) {
- // 获得当前导航栏对象
- UINavigationItem *item = [self navigationItem];
- // 设置导航栏左按钮
- UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithTitle:@"leftButton" style:UIBarButtonItemStylePlain target:self action:@selector(buttonClickedAction:)];
- [leftButton setTag:];
- [item setLeftBarButtonItem:leftButton animated:YES];
- // 设置导航栏右按钮
- UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithTitle:@"rightButton" style:UIBarButtonItemStyleDone target:self action:@selector(buttonClickedAction:)];
- [rightButton setTag:];
- [item setRightBarButtonItem:rightButton animated:YES];
- }
- return self;
- }
我们在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方法实现如下:
- - (void)viewDidLoad
- {
- // Do any additional setup after loading the view.
- [super viewDidLoad];
- if (!adapter) {
- [self initData];
- // 生成适配器委托对象
- adapter = [[TableViewAdapter alloc] initWithSource:data Controller:self];
- // 设置适配器委托对象
- [[self tableView] setDelegate:adapter];
- [[self tableView] setDataSource:adapter];
- }
- }
如上,在viewDidLoad中,我先去初始化数据(demo中的实现其实就是循环了14次,往data中加了14个Person对象),然后生成一个适配器委托对象,传入data(数据源)和self(当前controller对象),相当于android中的 adapter = new MyAdapter(list, this);有木有??!!
然后,setDelegate用来设置委托对象(相当于android中的listView.setAdapter(adapter)),setDataSource用来设置数据源。
这里,完整地列出NaviRootController的代码:
- //
- // NaviRootController.m
- // TabViewTest
- //
- // Created by WANGJIE on 13-10-31.
- // Copyright (c) 2013年 WANGJIE. All rights reserved.
- //
- #import "NaviRootController.h"
- #import "Person.h"
- #import "TableViewAdapter.h"
- @interface NaviRootController ()
- @end
- @implementation NaviRootController
- @synthesize data, adapter;
- - (id)init
- {
- self = [self initWithNibName:@"navi_root" bundle:nil];
- return self;
- }
- - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
- {
- self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
- if (self) {
- // Custom initialization
- // 获得当前导航栏对象
- UINavigationItem *item = [self navigationItem];
- // 设置导航栏左按钮
- UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithTitle:@"leftButton" style:UIBarButtonItemStylePlain target:self action:@selector(buttonClickedAction:)];
- [leftButton setTag:];
- [item setLeftBarButtonItem:leftButton animated:YES];
- // 设置导航栏右按钮
- UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithTitle:@"rightButton" style:UIBarButtonItemStyleDone target:self action:@selector(buttonClickedAction:)];
- [rightButton setTag:];
- [item setRightBarButtonItem:rightButton animated:YES];
- }
- return self;
- }
- /**
- * 初始化列表数据
- */
- - (void)initData
- {
- data = [[NSMutableArray alloc] init];
- NSLog(@"%@", NSStringFromSelector(_cmd));
- Person *person;
- for (int i = ; i < ; i++) {
- person = [[Person alloc] init];
- [person setName:[@"name" stringByAppendingString:
- [NSString stringWithFormat:@"%d", i]
- ]
- ];
- [person setAge:i + ];
- [person setPic:[UIImage imageNamed:@"Hypno.png"]];
- [data addObject:person];
- }
- }
- - (void)viewDidLoad
- {
- // Do any additional setup after loading the view.
- [super viewDidLoad];
- if (!adapter) {
- [self initData];
- // 生成适配器委托对象
- adapter = [[TableViewAdapter alloc] initWithSource:data Controller:self];
- // 设置适配器委托对象
- [[self tableView] setDelegate:adapter];
- [[self tableView] setDataSource:adapter];
- }
- }
- - (void)viewWillAppear:(BOOL)animated
- {
- [super viewWillAppear:YES];
- }
- - (void)didReceiveMemoryWarning
- {
- [super didReceiveMemoryWarning];
- // Dispose of any resources that can be recreated.
- }
- // 按钮点击事件回调
- - (void)buttonClickedAction:(id)sender
- {
- NSString *message;
- switch ([sender tag]) {
- case :
- message = @"left button clicked!";
- break;
- case :
- message = @"right button clicked!";
- break;
- default:
- message = @"unknow button clicked!";
- break;
- }
- UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"alert view" message:message delegate:self cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil];
- [alert show];
- }
- @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:这个方法,它的实现如下所示:
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- Person *p = [data objectAtIndex:[indexPath row]];
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyTableCell"];
- if (!cell) {
- NSArray *nibViews = [[NSBundle mainBundle] loadNibNamed:@"table_cell" owner:self options:nil];
- cell = [nibViews objectAtIndex:];
- }
- UIImageView *picIv = (UIImageView *)[cell viewWithTag:];
- UILabel *nameLb = (UILabel *)[cell viewWithTag:];
- UILabel *ageLb = (UILabel *)[cell viewWithTag:];
- [nameLb setText:[p name]];
- [ageLb setText:[NSString stringWithFormat:@"%d", [p age]]];
- [picIv setImage:[p pic]];
- return cell;
- }
如上图所示:
第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代码如下:
- //
- // TableViewAdapter.m
- // TabViewTest
- //
- // Created by WANGJIE on 13-10-31.
- // Copyright (c) 2013年 WANGJIE. All rights reserved.
- //
- #import "TableViewAdapter.h"
- #import "Person.h"
- #import "NaviRootController.h"
- @implementation TableViewAdapter
- - (id)initWithSource:(NSMutableArray *)source Controller:(NaviRootController *)context
- {
- NSLog(@"initWithSource...");
- self = [super init];
- if (self) {
- data = source;
- controller = context;
- }
- return self;
- }
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- {
- return [data count];
- }
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- NSLog(@"%@", NSStringFromSelector(_cmd));
- // UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
- // if (!cell) {
- // cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"UITableViewCell"];
- // }
- // 获得当前要显示的数据
- Person *p = [data objectAtIndex:[indexPath row]];
- //
- // [[cell textLabel] setText:[p name]];
- // 记得在cell.xib文件中设置identifier以达到重用的目的
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyTableCell"];
- if (!cell) {
- // 通过mainBundle的loadNibNamed方法去加载布局,类似android中的LayoutInflater.inflate()方法
- NSArray *nibViews = [[NSBundle mainBundle] loadNibNamed:@"table_cell" owner:self options:nil];
- cell = [nibViews objectAtIndex:];
- // cell.selectionStyle = UITableViewCellSelectionStyleNone;
- }
- // 通过在cell.xib中各控件设置的tag来获得控件,类似android中的findViewByTag/findViewById
- UIImageView *picIv = (UIImageView *)[cell viewWithTag:];
- UILabel *nameLb = (UILabel *)[cell viewWithTag:];
- UILabel *ageLb = (UILabel *)[cell viewWithTag:];
- [nameLb setText:[p name]];
- [ageLb setText:[NSString stringWithFormat:@"%d", [p age]]];
- [picIv setImage:[p pic]];
- return cell;
- }
- - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- {
- Person *person = [data objectAtIndex:[indexPath row]];
- UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"cell clicked!" message:[NSString stringWithFormat:@"%@ clicked!", [person name]]delegate:self cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil];
- [alert show];
- }
- - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- return [self tableView:tableView cellForRowAtIndexPath:indexPath].frame.size.height;
- }
- @end
好了!到此为止整个UITableView实现的流程基本完成了,可以看出跟android的ListView和GridView的实现流程很相似,理解了其中一个,另一个也能很好的理解它的工作流程。
最后来一张效果图:
【IOS】从android角度来实现(理解)IOS的UITableView的更多相关文章
- input 上传图片显示预览、调用摄像头,ios和Android的兼容性解决
html代码: <img id="pic" src="img/pic.png"/> </span><input id=" ...
- H5跟ios、android交互跟数据对接
需求: APP要用H5页面做展示,而且要获取到对应的商品ID,用户点击H5页面跳回APP原生页面. 方法: 先要判断用户是ios还是android设备(这里只考虑ios跟android,因为它俩写法还 ...
- ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开
ASP.NET MVC Filters 4种默认过滤器的使用[附示例] 过滤器(Filters)的出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每个请求都会响 ...
- ios和android一并学习的体会
如果说为什么要同时学习这两种不同的移动平台,其实有一定的“闲”的因素在里面. 相对于ios,android我是早半年接触的.最开始学习的时候也就是j2ee学习的延续,通过看视频连带看书学了大概一个月的 ...
- 简谈WP,IOS,Android智能手机OS
什么是智能手机? 相信到现在这个已经是傻瓜到不能再傻瓜的问题了 智能手机都不懂? 那你活着还有什么意思= = 但是为了谈论今天的三大主角:wp,ios,android 不得不回答一下这个笨笨的问题 如 ...
- 开园第一篇 - 论移动开发环境 IOS与Android的差异
首先,在真正写技术之前做个自我简介.本人08年开始学c语言 一年后,转vc++.开始接触MFC MFC做了两年.转眼11年了我考上了一个不知名的大专.搞C++发现没有市场了因为当时酷狗腾讯的软件已经日 ...
- 基于IOS和Android设备MDM技术方案服务价格
导读:前段时间 www.mbaike.net 博客被恶意攻击,导致程序崩溃,目前已经替换了以前的Wordpress程序,现提供IOS和Android版本MDM的代码和相关文档咨询服务. 一.IOS版M ...
- 【转】针对iOS VS. Android开发游戏的优劣——2013-08-25 17
http://game.dapps.net/gamedev/experience/8670.html 问题:如果你正在一个新工作室开发一款新的平板/手机游戏,你会选择iOS还是Android? 回答: ...
- 基于iOS,Android的服务器证书失效检测
1.前言 在目前的iOS,Android手机上,当手机应用进行SSL通信时,手机端默认是不会进行服务器证书是否失效的监测. 在iOS上,系统是会定期获取所访问服务器的证书信息然后出存在本地. 在And ...
随机推荐
- 测试Servlet生命周期学习笔记
测试环境:windows xp旗舰版 软件环境:myclipse8.5+tomcat7.0 ****************************************************** ...
- Windows Azure Service Bus (3) 队列(Queue) 使用VS2013开发Service Bus Queue
<Windows Azure Platform 系列文章目录> 在之前的Azure Service Bus中,我们已经介绍了Service Bus 队列(Queue)的基本概念. 在本章中 ...
- JAVA - JAVA编译运行过程
Java编译原理 *.java→*.class→机器码 java编译器 (编译) → 虚拟机(解释执行) → 解释器(翻译) → 机器码 1.Java编译过程与c/c++编译过程不同 Java编译程 ...
- StgCreateDocfileOnILockBytes复合文档
CRichEditCtrl 的ole技术 ------------ IRichEditOle --------------------------- 如需向CRichEditCtrl里面插入Ole对象 ...
- CaptureManagerSDK
Simple SDK for capturing, recording and streaming video and audio from web-cams on Windows OS by Win ...
- 从C#到Objective-C,循序渐进学习苹果开发(7)--使用FMDB对Sqlite数据库进行操作
本随笔系列主要介绍从一个Windows平台从事C#开发到Mac平台苹果开发的一系列感想和体验历程,本系列文章是在起步阶段逐步积累的,希望带给大家更好,更真实的转换历程体验.本篇主要开始介绍基于XCod ...
- C# 如何调用WebServer函数
WebServer(ASMX)服务程序网站的编写简单总结. WebServer中遇到的问题 编写WebServer程序VS运行调试时如果出现 System.Data.OracleClient.Orac ...
- OS初识
参考: 操作系统的故事(1-4):
- .NET vs2010中使用IrisSkin2.dll轻松实现winForm窗体换肤功能
IrisSkin2.dll是一款很不错的免费皮肤控件,利用它可以轻松的实现winForm窗体换肤! 网上很多朋友说在VS2010中不能使用IrisSkin2.dll,我这里提供一个取巧的办法. Iri ...
- 不可或缺 Windows Native (11) - C++: hello c++, C++ 与 C语言的区别小介
[源码下载] 不可或缺 Windows Native (11) - C++: hello c++, C++ 与 C语言的区别小介 作者:webabcd 介绍不可或缺 Windows Native 之 ...