一:UITableView基础知识点。
首先理解各个代理方法的调用顺序,这对于开发非常重要,理解好这一点,在项目开发中将会节省很多时间。下面我们一起来看一下,错误的地方,希望大家指出,一起学习。(最好的理解方式就是自己写一个小demo)
//情况一:单分区情况
1)首先调用-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView方法计算UITableView中分区的个数.
2)执行-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section方法,计算每个分区中row的个数.
3)计算每个cell的高度,cell有多少个,该方法- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath就被执行多少次.上面过程被反复执行了3次.
4)然后调用-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath创建cell,和显示cell的内容.屏幕中可以显示多少个cell,该方法就会被调用多少次.并且同时还会调用计算高度方法计算该cell的高度.
5)当滚动屏幕出现新的cell的时候,首先会先调用-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath显示cell的内容.然后立马计算 cell的高度.
[self.tableView reloadData];使用该方法进行刷新数据的时候,会按照刚刚执行方法的顺序,再执行一次.
//多分区情况
1: 先确定分区个数调用该 -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView方法,及Section个数.
2: 调用-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
从scetion等于1开始(我这里是使用了2个分区,多分区从最后一个开始自动计算,依次向前).section为1时,计算分区中cell的高度,section为0时计算分区中cell的高度.
3: 然后调用-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath创建cell,和显示cell的内容.屏幕中可以显示多少个cell,该方法就会被调用多少次.从分区0开始,只计算屏幕上显示的内容.
4:滑动屏幕的时候,调用-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath显示内容,同时调用- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath方法计算高度.
[self.tableView reloadData];使用该方法进行刷新数据的时候,会按照刚刚执行方法的顺序,再执行一次.
1)看代码理解基本的属性和方法,下面代码实现了最基本的列表展示,移动表格可看见分区头视图进行替换。
#import "RootViewController.h"
@interface RootViewController ()
{
NSMutableArray *_dataArray;//数据源:给UITableView提供数据
}
@end
@implementation RootViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [superinitWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[superviewDidLoad];
//准备数据
_dataArray = [[NSMutableArrayalloc] init];
//实现通讯录的展示
for (int i='A'; i<='Z';i++) {
NSMutableArray *array = [[NSMutableArrayalloc] init];
for (int j=0; j<10; j++) {
NSString *str = [NSStringstringWithFormat:@"%c%d",i,j];
[arrayaddObject:str];
}
[_dataArrayaddObject:array];
}
self.automaticallyAdjustsScrollViewInsets =NO;
//表视图,是一个特殊的UIScrollView,默认横向固定,纵向可以上下滚动,contentSize自动计算
//创建tableView并设置样式(常规的样式)
//表示图,是一个特殊到uiscrollview,默认横向固定,纵向可以上下滚动。contentsize自动计算,不需要你来设置。UITableViewStylePlain常规显示样式。
UITableView *tableView = [[UITableViewalloc] initWithFrame:CGRectMake(0,0,self.view.bounds.size.width,self.view.bounds.size.height-64)style:UITableViewStylePlain];
//设置代理 (代理方法都是与tableView UI样式相关的方法)//设置tabview都数据源,协议中有2个必须实现都方法,所以要记住,使用来数据源加要注意方法实现,而且很重要。
tableView.delegate =self;
//设置tableView的数据源(数据源方法一般都是与UITableView数据操作有关的方法)
tableView.dataSource =self;
[self.viewaddSubview:tableView];
}
#pragma mark - UITableViewDelegate
//设置行高,不写此方法,默认高度44
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 50;
}
//选中tableView中的某一行时,触发此方法
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(@"selected section:%d row:%d",indexPath.section,indexPath.row);
//通过tableView设置某一行,自动反选,反选的代理方法不再被调用
[tableView deselectRowAtIndexPath:indexPathanimated:YES];
//这里一般情况下是push到下一级进行相应的操作。
}
//某一行被反选时,触发此方法当某一行由选中变为非选中状态会调用此方法。
//即某一行被反选时,触发次方法
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(@"deselect section:%d row:%d",indexPath.section,indexPath.row);
}
#pragma mark - UITableViewDataSource
//告诉tableView有多少个分区(数据有多少类)
//如果不写此方法,默认tableView有一个分区
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return_dataArray.count;
}
//告诉tableView每个分区有多少条(行)数据
//有多少个分区 此方法被执行多少次,section在方法执行时从0自增
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [[_dataArrayobjectAtIndex:section] count];
}
//tableView中每一行都是一个UITableViewCell对象:单元格,本质上是一个视图
//一开始,屏幕中可见多少行,此方法被调用多少次,当上、下滑动屏幕时,此方法也会被调用
//cell的重用机制保证了只创建有限个cell对象,来显示多条数据,最大限度的节省了内存的开销,提高了程序的运行效率,具有极大的借鉴意义
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIde =@"Cell";//声明cell的可重用标识符
//根据可重用标识符,到tableView的重用队列中,取cell对象
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIde];
if (cell == nil) {
//如果取不到,则创建新的cell对象
//创建cell设置样式,并赋值可重用标识符
cell = [[UITableViewCellalloc] initWithStyle:UITableViewCellStyleSubtitlereuseIdentifier:cellIde];
}
//设置cell的选中样式,iOS7之前,cell的选中样式默认为蓝色,iOS7之后
cell.selectionStyle =UITableViewCellSelectionStyleGray;
//设置cell右侧的提示样式
//UITableViewCellAccessoryDisclosureIndicator箭头提示
cell.accessoryType =UITableViewCellAccessoryDisclosureIndicator;
//将数据,赋值给cell
//indexPath section带有该行所在的分区信息 row该行在对应分区的位置
//取到该分区所使用的数组
NSArray *array = [_dataArrayobjectAtIndex:indexPath.section];
NSString *str = [array objectAtIndex:indexPath.row];
//将数组赋值给cell
//UITableViewCellStyleSubtitle 副标题才能显示
//设置cell的主标题
cell.textLabel.text = str;
//设置副标题
cell.detailTextLabel.text =@"test";
//设置图片(头像)
cell.imageView.image = [UIImageimageNamed:@"0.png"];
return cell;
}
//设置分区的头标题
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
return [NSStringstringWithFormat:@"第%c分区头标",'A'+section];
}
二:项目中常用到UITableView的一些小知识点:
1)UITableViewCell横线左端对其实现:
方法一:在初始化的表格的时候添加如下代码
if ([_tableViewrespondsToSelector:@selector(setSeparatorInset:)]) {
[_tableViewsetSeparatorInset:UIEdgeInsetsZero];
}
if ([_tableViewrespondsToSelector:@selector(setLayoutMargins:)]) {
[_tableViewsetLayoutMargins:UIEdgeInsetsZero];
}
实现代理方法
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if ([cell respondsToSelector:@selector(setSeparatorInset:)]) {
[cellsetSeparatorInset:UIEdgeInsetsZero];
}
if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
[cellsetLayoutMargins:UIEdgeInsetsZero];
}
}
方法二:去除表格细线,然后自定义细线进行添加
_tableView.separatorStyle =UITableViewCellSeparatorStyleNone;
//添加细线
CALayer *lineLayer=[CALayerlayer];
lineLayer.frame=CGRectMake(0,43.5,self.view.bounds.size.width,0.5);//位置根据需求自己设置
lineLayer.backgroundColor=[UIColorgrayColor].CGColor;
[self.view.layeraddSublayer:lineLayer];
//选中tableView中的某一行时,触发此方法
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//通过tableView设置某一行,自动反选,反选的代理方法不再被调用
[tableView deselectRowAtIndexPath:indexPathanimated:YES];
}
这两个方法,是配合起来使用的,标记了一个tableView的动画块。分别代表动画的开始开始和结束。
两者成对出现,可以嵌套使用。一般,在添加,删除,选择 tableView中使用,并实现动画效果。
在动画块内,不建议使用reloadData方法,如果使用,会影响动画。
插入指定的行,
在执行该方法时,会对数据源进行访问(分组数据和行数据),并更新可见行。所以,在调用该方法前,应该先更新数据源
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
插入分组到制定位置
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
插入一个特定的分组。如果,指定的位置上已经存在了分组,那么原来的分组向后移动一个位置。
删除制定位置的分组
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
删除一个制定位置的分组,其后面的分组向前移动一个位置。
删除选择的row
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
移动分组
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
移动原来的分组从一个位置移动到一个新的位置。如果,新位置上若存在某个分组,那这某个分组将会向上(下)移动到临近一个位置。该方法,没有动画参数。会直接移动。并且一次只能移动一个分组。
在如上方法中,建议使用该动画块进行操作!
具体实现部分代码:
删除部分操作:(插入原理其实是一样的,保证数据源随之更新即可)
[self.tableView beginUpdates];
//要记得对分区进行删除操作。self.dataArray是一个二维数组。及实现多分区情况下的删除操纵。
if([[self.dataArray objectAtIndex: indexPath.section] count] > 1)
{
[_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section]] withRowAnimation:UITableViewRowAnimationLeft];
}
else
{
//如果我们的UITableView是分组的时候,我们如果删除某个分组的最后一条记录时,相应的分组也将被删除。所以,必须保证UITableView的分组,和cell同时被删除。
[_tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]
withRowAnimation:UITableViewRowAnimationLeft];
}
//注意:数据源在beginUpdates前面操作和在后面操作,并没有什么关系,关键一点是数据源要随着删除,插入的数据与之对应改变,不然就会出现错误。但是不能够把对数据源的操作放到endUpdates。
NSMutableArray *arr=self.dataArray[indexPath.section];
[arr removeObjectAtIndex:indexPath.row];
[self.dataArray removeObjectAtIndex:indexPath.section];
if(arr.count!=0)
[self.dataArray insertObject:arr atIndex:indexPath.section];
[self.tableView endUpdates];
static NSString *cellIde =@"Cell";//声明cell的可重用标识符
//根据可重用标识符,到tableView的重用队列中,取cell对象
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIde];
if (cell == nil) {
//如果取不到,则创建新的cell对象,创建cell设置样式,并赋值可重用标识符
cell = [[[UITableViewCellalloc] initWithStyle:UITableViewCellStyleSubtitlereuseIdentifier:cellIde]autorelease];
}
但有时候系统自带的cell无法满足需求的时候,需要自定义UITableViewCell(用一个子类继承UITableViewCell,自定义UITableViewCell很重要,要掌握),而且每一行用的不一定是同一种UITableViewCell,所以一个UITableView可能拥有不同类型的UITableViewCell,对象池中也会有很多不同类型的UITableViewCell,那么UITableView在重用UITableViewCell时可能会得到错误类型的UITableViewCell。
解决方案:UITableViewCell有个NSString *reuseIdentifier属性,可以在初始化UITableViewCell的时候传入一个特定的字符串标识来设置reuseIdentifier9。当UITableView要求dataSource返回UITableViewCell时,先通过一个字符串标识到对象池中查找对应类型的UITableViewCell对象,如果有,就重用,如果没有,就传入这个字符串标识来初始化一个UITableViewCell对象。
重用UITableViewCell对象:
在ios5 和io6中分别提供了方法可以先注册cell,注册后就不需要在判断cell是否为空
- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
注:
去对象池中取可重用的cell,系统提供了2个方法,分别是
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);
这2个方法如果在已经注册的情况下作用是一样的。
区别在于后一个方法会强制需要注册,否则会报错,即使代码中有判断cell为空得情况。
2 自定义UITableViewCell
一般有两种方式:
①用一个xib文件来描述UITableViewCell的内容。
②通过代码往UITableViewCell的contentView中添加子视图,在初始化方法(比如init、initWithStyle:reuseIdentifier:)中添加子控件,在layoutSubviews方法中分配子控件的位置和大小。
方法一:
1:首先计算需要显示在界面上cell的大小,然后显示相应的内容。当新出现一个cell的时候,去重用列队拿一个cell。然后去除上面显示的所有子视图(但是这样创建对象的消耗也会相应增加)。在重新创建对应位置的控件,进行赋值操作。
2:在初始化的时候,只需要创建对应的控件即可。然后在进行数据操作的时候设置相应控件的参数。
3:出现新cell的时候机会先调用显示,然后在调用高度。所以只要保证先获取对应位置cell的高度,然后在对各个控件进行赋值操作。
注意:每一个控件都要是全局的变量,这样才可以在该界面一直使用。
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated;//使用这个方法即可
[tableView scrollToRowAtIndexPath:[NSIndexPathindexPathForRow:0inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];//就是这个效果点击 button 之后回到顶部
7):在UITableviewCell上知道自己点击了哪一个 button
-(void)buttonPressed:(id)sender { //button点击事件
UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
NSIndexPath *clickedButtonPath = [self.tableView indexPathForCell:clickedCell];
...
}
方法二:
- (void)buttonTappedAction:(id)sender {
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
...
}
8) 删除重用的cell的所有子视图从而得到一个没有特殊格式的cell,供其他cell重用。(可以很好的把对cell有过操作的效果全部去掉,然后拿过来重新使用,要保持之前的状态,只需要之前先存储好,在调用代理方法的时候获取数据显示即可)
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier];
}
else
{
//删除cell的所有子视图
while ([cell.contentView.subviews lastObject] != nil)
{
[(UIView*)[cell.contentView.subviews lastObject] removeFromSuperview];
}
}
[tableView setSeparatorColor:[UIColor redColor]];
改变UITableViewCell选中时背景色:
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.frame];
cell.selectedBackgroundView.backgroundColor = [UIColor redColor];