iOS UITableView 与 UITableViewController实例详解
很多应用都会在界面中使用某种列表控件:用户可以选中、删除或重新排列列表中的项目。这些控件其实都是UITableView对象,可以用来显示一组对象,例如,用户地址薄中的一组人名。
UITableView对象虽然只能显示一行数据,但是没有行数限制。
•编写新的应用程序JXHomepwner应用
创建应用,填写基本信息
•UITableViewController
UITableView是视图。我们知道模型-视图-控制器(Model-View-Controller),他是我们必须遵守的一种设计模式。其含义是,应用创建的任何一个对象,其类型必定是以下三种类型中的一种。
1.模型:负责存储数据,与用户界面无关。
2.视图:负责显示界面,与模型对象无关。
3.控制器:负责确保视图对象和模型对象的数据保持一致。
一般来说,作为视图对象的UITableView不应该负责处理应用的逻辑或数据。当在应用中使用UITableView对象的时候,必须考虑如何大啊呸其他的对象,与UITableView对象一起工作:
通常情况下,要通过某个视图控制器对象来创建和释放UITableView对象,并负责显示或者隐藏视图。
UITableView对象要有数据源才能正常工作。UITableView对象会向数据源查询要显示的行数,显示表格行所需要的数据和其他所需要的数据。没有数据源的UITableView对象只是空壳。凡是遵守UITableViewDataSource协议的对象,都可以成为UITableView对象的数据源(即dataSource属性所指向的对象)。
通常情况下,要为UITableView对象设置委托对象,以便能在该对象发生特定事件的时候做出相应的处理。凡是遵守UITableViewDelegate协议的对象,都可以成为UITableView对象的委托对象。
UITableViewController对象可以扮演以上全部角色,包括视图控制器对象、数据源和委托对象。
UITableViewController是UIViewController的子类,所以也有view属性。UITableViewController对象的view属性指向一个UITableView对象,并且这个对象由UITableViewController对象负责设置和显示。UITableViewController对象会在创建UITableView对象后,为这个UITableView对象的dataSource和delegate赋值,并指向自己。
•创建UITableViewController子类
下面要为我们创建的程序编写一个UITableViewController子类。
UITableViewController的指定初始化方法是initWithStyle:调用initWithStyle:时要传入一个类型作为UITableViewStyle的常熟,该常熟决定了UITableView对象的风格。目前可以使用的UITableViewStyle常量有两个,即UITableViewStylePlain和UITableViewStyleGrouped。
现在将UITableViewController的指定初始化方法改为init:,为此时需要遵守两条规则:
1.在新的指定初始化方法中调用父类的指定初始化方法。
2.覆盖父类的初始化方法,调用新的指定初始化方法。
#import"JXItemsViewController.h" @interfaceJXItemsViewController() @end @implementationJXItemsViewController -(instancetype)init{ //调用父类的指定初始化方法 self=[superinitWithStyle:UITableViewStylePlain]; returnself; } -(instancetype)initWithStyle:(UITableViewStyle)style{ return[selfinit]; } @end
实现以上两个初始化方法之后,可以确保无论向新创建的JXItemsViewController对象发送哪一个初始化方法,初始化后的对象都会使用我们指定的风格。
接下来代码如下:
#import"AppDelegate.h" #import"JXItemsViewController.h" @interfaceAppDelegate() @end @implementationAppDelegate -(BOOL)application:(UIApplication*)applicationdidFinishLaunchingWithOptions:(NSDictionary*)launchOptions{ self.window=[[UIWindowalloc]initWithFrame:[UIScreenmainScreen].bounds]; //添加初始化代码 //创建JXItemsViewController对象 JXItemsViewController*itemsViewController=[[JXItemsViewControlleralloc]init]; //将JXItemsViewController的标示图加入窗口 self.window.rootViewController=itemsViewController; self.window.backgroundColor=[UIColorwhiteColor]; [self.windowmakeKeyAndVisible]; returnYES; }
构建并运行应用我们确实能在屏幕上看到UITableView对象。JXItemsViewController作为UITableViewController的子类,集成了view方法。view方法会调用loadView方法,如果视图不存在,则loadView方法会创建并载入一个空的视图。
下面我们要为UITableView设置内容。
新建JXItem类
#import<Foundation/Foundation.h> @interfaceJXItem:NSObject /**创建日期*/ @property(nonatomic,strong,readonly)NSDate*createDate; /**名称*/ @property(nonatomic,strong)NSString*itemName; /**编号*/ @property(nonatomic,strong)NSString*serialnumber; /**价值*/ @property(nonatomic,assign)NSIntegervalueInDollars; /**JXImageStore中的键*/ @property(nonatomic,strong)NSString*itemKey; +(instancetype)randomItem; /** *JXItem类指定的初始化方法 *@return类对象 */ -(instancetype)initWithItemName:(NSString*)name valueInDollars:(NSInteger)value serialNumber:(NSString*)sNumber; -(instancetype)initWithItemName:(NSString*)name; @end #import"JXItem.h" @implementationJXItem +(instancetype)randomItem{ //创建不可变数组对象,包含三个形容词 NSArray*randomAdjectiveList=@[ @"Fluffy", @"Rusty", @"Shiny" ]; //创建不可变数组对象,包含三个名词 NSArray*randomNounList=@[ @"Bear", @"Spork", @"Mac" ]; //根据数组对象所含的对象的个数,得到随机索引 //注意:运算符%是模运算符,运算后得到的是余数 NSIntegeradjectiveIndex=arc4random()%randomAdjectiveList.count; NSIntegernounIndex=arc4random()%randomNounList.count; //注意,类型为NSInteger的变量不是对象 NSString*randomName=[NSStringstringWithFormat:@"%@%@",randomAdjectiveList[adjectiveIndex],randomNounList[nounIndex]]; NSIntegerrandomValue=arc4random_uniform(100); NSString*randomSerialNumber=[NSStringstringWithFormat:@"%c%c%c%c", '0'+arc4random_uniform(10), 'A'+arc4random_uniform(26), '0'+arc4random_uniform(10), 'A'+arc4random_uniform(26)]; JXItem*newItem=[[selfalloc]initWithItemName:randomName valueInDollars:randomValue serialNumber:randomSerialNumber]; returnnewItem; } -(NSString*)description{ NSString*descriptionString=[NSStringstringWithFormat:@"%@(%@):Worth$%zd,recordedon%@",self.itemName,self.serialnumber,self.valueInDollars,self.createDate]; returndescriptionString; } -(instancetype)initWithItemName:(NSString*)name valueInDollars:(NSInteger)value serialNumber:(NSString*)sNumber{ //调用父类的指定初始化方法 self=[superinit]; //父类的指定初始化方法是否成功创建了对象 if(self){ //为实例变量设置初始值 _itemName=name; _valueInDollars=value; _serialnumber=sNumber; //设置_createDate为当前时间 _createDate=[NSDatedate]; //创建一个NSUUID对象 NSUUID*uuid=[[NSUUIDalloc]init]; NSString*key=[uuidUUIDString]; _itemKey=key; } //返回初始化后的对象的新地址 returnself; } -(instancetype)initWithItemName:(NSString*)name{ return[selfinitWithItemName:namevalueInDollars:0serialNumber:@""]; } -(instancetype)init{ return[selfinitWithItemName:@"Item"]; } -(void)dealloc{ NSLog(@"Destoryed:%@",self); } @end
•UITableView数据源
创建JXItemStore
JXItemStore对象是一个单例,也就是说,每个应用只会有一个这种类型的对象。如果应用尝试创建另一个对象,JXItemStore类就会返回已经存在的那个对象。当某个程序要在很多不同的代码段中使用同一个对象时,将这个对象设置为单例是一种很好的设计模式,只需要向该对象的类发送特定的方法,就可以得到相同的对象。
#import<Foundation/Foundation.h> @interfaceJXItemStore:NSObject //注意,这是一个类方法,前缀是+ +(instancetype)sharedStore; @end 在JXItemStore类收到sharedStore消息后,会检查自己是否已经创建JXItemStore的单例对象。如果已经创建,就返回自己已经创建的对象,否则就需要先创建,然后再返回。 #import"JXItemStore.h" @implementationJXItemStore //单粒对象 +(instancetype)sharedStore{ staticJXItemStore*sharedStore=nil; //判断是否需要创建一个sharedStore对象 if(!sharedStore){ sharedStore=[[selfalloc]init]; } returnsharedStore; } @end
这段代码将sharedStore指针声明了静态变量。当某个定义了静态变量的方法返回时,程序不会释放相应的变量。静态变量和全局变量一样,并不是保存在栈中的。
sharedStore变量的初始值为nil。当程序第一次执行sharedStore方法时,会创建一个JXItemStore对象,并将新创建的对象的地址赋值给sharedStore变量。当程序再次执行sharedStore方法时,不管是第几次,其指针总是指向最初创建的那个对象。因为指向JXItemStore对象的sharedStore变量是强引用,且程序永远不会释放该变量,所以sharedStore变量所指向的JXItemStore对象永远也不会被释放。
JXItemsViewController需要创建一个新的JXItem对象时会向JXItemStore对象发送消息,收到消息的JXItemStore对象会创建一个JXItem对象并将其保存到一个JXItem数组中,之后JXItemsViewController可以通过该数组获取所有JXItem对象,并使用这些对象填充自己的表视图。
#import<Foundation/Foundation.h> @classJXItem; @interfaceJXItemStore:NSObject /**存放JXItem对象数组*/ @property(nonatomic,readonly)NSArray*allItem; //注意,这是一个类方法,前缀是+ +(instancetype)sharedStore; -(JXItem*)createItem; @end
在实现文件中编辑。但是我们需要注意,在我们的应用中将使用JXItemStore管理JXItem数组-包括添加、删除和排序。因此,除JXItemStore之外的类不应该对JXItem数组做这些操作。在JXItemStore内部,需要将JXItem数组定义为可变数组。而对其他类来说,JXItem数组则是不可变的数组。这是一种常见的设计模式,用于设置内部数据的访问权限:某个对象中有一种可修改的数据,但是除该对象本身之外,其他对象只能访问该数据而不能修改它。
#import"JXItemStore.h" #import"JXItem.h" @interfaceJXItemStore() /**可变数组,用来操作JXItem对象*/ @property(nonatomic,strong)NSMutableArray*privateItems; @end @implementationJXItemStore //单粒对象 +(instancetype)sharedStore{ staticJXItemStore*sharedStore=nil; //判断是否需要创建一个sharedStore对象 if(!sharedStore){ sharedStore=[[selfalloc]init]; } returnsharedStore; } -(NSArray*)allItem{ returnself.privateItems; } #pragmamark-懒加载 -(NSMutableArray*)privateItems{ if(_privateItems==nil){ _privateItems=[[NSMutableArrayalloc]init]; } return_privateItems; } @end
allItem方法的返回值是NSArray类型,但是方法体中返回的是NSMutableArray类型的对象,这种写法是正确的,因为NSMutableArray是NSArray子类。
这种写法可能会引起一个问题:虽然头文件中将allItem的类型声明为NSArray,但是其他对象调用JXItemStore的allItem方法时,得到的一定是一个NSMutableArray对象。
使用像JXItemStore这样的类时,应该遵守其头文件中的声明使用类的属性和方法。例如,在JXItemStore头文件中,因为allItem属性的类型是NSArray,所以应该将其作为NASrray类型的对象使用。如果将allItem转换为NSMutableArray类型并修改其内容,就违反了JXItemStore头文件中的声明。可以通过覆盖allItem方法避免其他类修改allItem。
#import"JXItemStore.h" #import"JXItem.h" @interfaceJXItemStore() /**可变数组,用来操作JXItem对象*/ @property(nonatomic,strong)NSMutableArray*privateItems; @end @implementationJXItemStore //单粒对象 +(instancetype)sharedStore{ staticJXItemStore*sharedStore=nil; //判断是否需要创建一个sharedStore对象 if(!sharedStore){ sharedStore=[[selfalloc]init]; } returnsharedStore; } -(NSArray*)allItem{ return[self.privateItemscopy]; } -(JXItem*)createItem{ JXItem*item=[JXItemrandomItem]; [self.privateItemsaddObject:item]; returnitem; } #pragmamark-懒加载 -(NSMutableArray*)privateItems{ if(_privateItems==nil){ _privateItems=[[NSMutableArrayalloc]init]; } return_privateItems; } @end
实现数据源方法
#import"JXItemsViewController.h" #import"JXItem.h" #import"JXItemStore.h" @interfaceJXItemsViewController() @end @implementationJXItemsViewController -(instancetype)init{ //调用父类的指定初始化方法 self=[superinitWithStyle:UITableViewStylePlain]; if(self){ for(NSIntegeri=0;i<5;i++){ [[JXItemStoresharedStore]createItem]; } } returnself; } -(instancetype)initWithStyle:(UITableViewStyle)style{ return[selfinit]; } @end
当某个UITableView对象要显示表格内容时,会向自己的数据源(dataSource属性所指向的对象)发送一系列消息,其中包括必须方法和可选方法。
@required -(NSInteger)tableView:(UITableView*)tableViewnumberOfRowsInSection:(NSInteger)section; //Rowdisplay.Implementersshould*always*trytoreusecellsbysettingeachcell'sreuseIdentifierandqueryingforavailablereusablecellswithdequeueReusableCellWithIdentifier: //Cellgetsvariousattributessetautomaticallybasedontable(separators)anddatasource(accessoryviews,editingcontrols) -(UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath; @optional -(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView;//Defaultis1ifnotimplemented -(nullableNSString*)tableView:(UITableView*)tableViewtitleForHeaderInSection:(NSInteger)section;//fixedfontstyle.usecustomview(UILabel)ifyouwantsomethingdifferent -(nullableNSString*)tableView:(UITableView*)tableViewtitleForFooterInSection:(NSInteger)section; //Editing //Individualrowscanoptoutofhavingthe-editingpropertysetforthem.Ifnotimplemented,allrowsareassumedtobeeditable. -(BOOL)tableView:(UITableView*)tableViewcanEditRowAtIndexPath:(NSIndexPath*)indexPath; //Moving/reordering //Allowsthereorderaccessoryviewtooptionallybeshownforaparticularrow.Bydefault,thereordercontrolwillbeshownonlyifthedatasourceimplements-tableView:moveRowAtIndexPath:toIndexPath: -(BOOL)tableView:(UITableView*)tableViewcanMoveRowAtIndexPath:(NSIndexPath*)indexPath; //Index -(nullableNSArray<NSString*>*)sectionIndexTitlesForTableView:(UITableView*)tableView__TVOS_PROHIBITED;//returnlistofsectiontitlestodisplayinsectionindexview(e.g."ABCD...Z#") -(NSInteger)tableView:(UITableView*)tableViewsectionForSectionIndexTitle:(NSString*)titleatIndex:(NSInteger)index__TVOS_PROHIBITED;//telltablewhichsectioncorrespondstosectiontitle/index(e.g."B",1)) //Datamanipulation-insertanddeletesupport //Afterarowhastheminusorplusbuttoninvoked(basedontheUITableViewCellEditingStyleforthecell),thedataSourcemustcommitthechange //NotcalledforeditactionsusingUITableViewRowAction-theaction'shandlerwillbeinvokedinstead -(void)tableView:(UITableView*)tableViewcommitEditingStyle:(UITableViewCellEditingStyle)editingStyleforRowAtIndexPath:(NSIndexPath*)indexPath; //Datamanipulation-reorder/movingsupport -(void)tableView:(UITableView*)tableViewmoveRowAtIndexPath:(NSIndexPath*)sourceIndexPathtoIndexPath:(NSIndexPath*)destinationIndexPath; @end
实现行数代码
#import"JXItemsViewController.h" #import"JXItem.h" #import"JXItemStore.h" @interfaceJXItemsViewController() @end @implementationJXItemsViewController -(instancetype)init{ //调用父类的指定初始化方法 self=[superinitWithStyle:UITableViewStylePlain]; if(self){ for(NSIntegeri=0;i<5;i++){ [[JXItemStoresharedStore]createItem]; } } returnself; } -(instancetype)initWithStyle:(UITableViewStyle)style{ return[selfinit]; } -(void)viewDidLoad{ [superviewDidLoad]; } #pragmamark-Tableviewdatasource -(NSInteger)tableView:(UITableView*)tableViewnumberOfRowsInSection:(NSInteger)section{ return[[[JXItemStoresharedStore]allItem]count]; } @end
UITableViewDataSource协议中的另外一个必须实现的方法
-(UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath
在此之前,我们需要先了解另一个类:UITableViewCell
•UITableViewCell对象
表视图所显示的每一行都是一个独立的视图,这些视图是UITableViewCell对象。其对象还有一个子视图:contentView。contentView也包含了很多子视图,他的子视图构成UITableViewCell对象的主要外观。此外,UITableViewCell对象还可以显示一个辅助指示图。辅助指示视图的作用是显示一个指定的图标,用于向用户提示UITableViewCell对象可以执行的动作。这些图标包括勾起标记、展开图标或中间有v形团的蓝色圆点。其默认是UITableViewCellAccessoryNone。
在创建UITableViewCell对象时,可以选择不同的风格来决定UITableViewCell对象显示。
typedefNS_ENUM(NSInteger,UITableViewCellStyle){ UITableViewCellStyleDefault,//Simplecellwithtextlabelandoptionalimageview(behaviorofUITableViewCelliniPhoneOS2.x) UITableViewCellStyleValue1,//Leftalignedlabelonleftandrightalignedlabelonrightwithbluetext(UsedinSettings) UITableViewCellStyleValue2,//Rightalignedlabelonleftwithbluetextandleftalignedlabelonright(UsedinPhone/Contacts) UITableViewCellStyleSubtitle//Leftalignedlabelontopandleftalignedlabelonbottomwithgraytext(UsediniPod). };//availableiniPhoneOS3.0
创建并获取UITableViewCell对象
下面我们主要对tableView:cellForRowAtIndexPath:方法进行改写。首先我们需要将JXItem数据跟UITableViewCell对象对应起来。在方法中有一个实参是NSIndexPath对象,该对象包含两个属性section(段)和row(行)。当UITableView对象向其数据源发送tableView:cellForRowAtIndexPath:消息时,其目的是获取显示第section个表格段、第row行数据的UITableViewCell对象。
#import"JXItemsViewController.h" #import"JXItem.h" #import"JXItemStore.h" @interfaceJXItemsViewController() @end @implementationJXItemsViewController -(instancetype)init{ //调用父类的指定初始化方法 self=[superinitWithStyle:UITableViewStylePlain]; if(self){ for(NSIntegeri=0;i<15;i++){ [[JXItemStoresharedStore]createItem]; } } returnself; } -(instancetype)initWithStyle:(UITableViewStyle)style{ return[selfinit]; } -(void)viewDidLoad{ [superviewDidLoad]; } #pragmamark-Tableviewdatasource -(NSInteger)tableView:(UITableView*)tableViewnumberOfRowsInSection:(NSInteger)section{ return[[[JXItemStoresharedStore]allItem]count]; } -(UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath{ //创建UITableViewCell对象,风格使用默认风格 UITableViewCell*cell=[[UITableViewCellalloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"UITableViewCell"]; //获取allItem的第n个JXItem对象 //然后将该JXItem对象的描述信息赋值给UITableViewCell对象的textLabel //这里的n是该UITableViewCell对象所对应的表格索引 NSArray*items=[[JXItemStoresharedStore]allItem]; JXItem*item=items[indexPath.row]; cell.textLabel.text=[itemdescription]; returncell; } @end
构建并运行
重用UITableViewCell对象
iOS设备内存是有限的,如果某个UITableView对象要显示大量的记录,并且要针对每条记录创建相应的UITableViewCell对象,就会很快耗尽iOS设备内存。
在UITableView上存在大量可优化的地方,其中最重要的就是关于UITableViewCell复用问题。因为当我们滑动界面是,大多数的cell表格都会移出窗口,移出窗口的UITableViewCell对象放入UITableViewCell对象池,等待重用。当UITableView对象要求数据源返回某个UITableViewCell对象时,就可以先查看对象池。如果有未使用的UITableViewCell对象,就可以用新的数据配置这个UITableViewCell对象,然后将其返回给UITableView对象,从而避免了创建新的对象,可以极大的优化内存。
但是这里还会有一个问题:如果我们在UITableView对象中创建了不同的UITableViewCell表格,用来展示不同的信息。那么这时候UITableViewCell对象池中的对象就会存在不同的类型,那么UItableView就有可能会得到错误的类型的UITableViewCell对象。鉴于上述原因,必须保证UITableView对象能够得到正确的指定类型的UITableViewCell对象,这样才能确定返回的对象会拥有哪些属性和方法。
从UITableViewCell对象池获取对象时,无需关心取回的是否是某个特性的对象,因为无论取回来的是哪个对象,都要重新设置数据。真正要关心的是取回来的对象是否是某个特性的类型。每个UITableViewCell对象都有一个类型为NSString的reuseIdentifier属性。当数据源向UITableView对象获取可重用的UITableViewCell对象时,可传入一个字符串并要求UITableView对象返回相应的UITableViewCell对象。
#import"JXItemsViewController.h" #import"JXItem.h" #import"JXItemStore.h" @interfaceJXItemsViewController() @end @implementationJXItemsViewController -(instancetype)init{ //调用父类的指定初始化方法 self=[superinitWithStyle:UITableViewStylePlain]; if(self){ for(NSIntegeri=0;i<15;i++){ [[JXItemStoresharedStore]createItem]; } } returnself; } -(instancetype)initWithStyle:(UITableViewStyle)style{ return[selfinit]; } -(void)viewDidLoad{ [superviewDidLoad]; //向控制器注册 [self.tableViewregisterClass:[UITableViewCellclass] forCellReuseIdentifier:@"UITableViewCell"]; } #pragmamark-Tableviewdatasource -(NSInteger)tableView:(UITableView*)tableViewnumberOfRowsInSection:(NSInteger)section{ return[[[JXItemStoresharedStore]allItem]count]; } -(UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath{ //创建UITableViewCell对象,风格使用默认风格 UITableViewCell*cell=[[UITableViewCellalloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"UITableViewCell"]; UITableViewCell*cell=[tableViewdequeueReusableCellWithIdentifier:@"UITableViewCell" forIndexPath:indexPath]; //获取allItem的第n个JXItem对象 //然后将该JXItem对象的描述信息赋值给UITableViewCell对象的textLabel //这里的n是该UITableViewCell对象所对应的表格索引 NSArray*items=[[JXItemStoresharedStore]allItem]; JXItem*item=items[indexPath.row]; cell.textLabel.text=[itemdescription]; returncell; } @end
以上所述是小编给大家介绍的iOSUITableView与UITableViewController实例详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!