iOS之UITableView
UITableView在app中的应用十分广泛,接下来我们就来简单学习一下UITableView。
一、基本认识
UITableView继承于UIScrollView,只不过前者只能纵向滑动。UITableView在父类的基础添加了一些属性:
(这些属性是没有遵守代理时的UITableView的本身的属性),这些属性在后面会介绍。
然后再来看看tableview的结构:
二、创建一个UITableView
首先我们在控制器的viewdidload方法中,创建一个UITableView(一般是将这个UITableView作为属性变量)。
//viewDidLoad中
self.tableView = [[UITableView alloc]
initWithFrame:CGRectMake(0, 64, self.view.bounds.size.width, self.view.bounds.size.height) style:
UITableViewStyleGrouped
];
需要指出的是UITableView的style属性有两个:UITableViewStyleGrouped,UITableViewStylePlain。二者之间的区别:二者的区别。
然后我们对这个UITableView本身的属性进行设置:
//ViewDidLoad中
//分割线颜色
self.tableView.separatorColor = [UIColor blackColor];
//分割线类型
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
//自适应的row的高度
self.tableView.estimatedRowHeight = 120;
self.tableView.rowHeight = UITableViewAutomaticDimension;
//背景颜色
self.tableView.backgroundColor = [UIColor whiteColor];
self.tableView.delegate = self;
//这个代理实际上是控制数据源的方法
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
分割线的颜色不用多说,是UIColor类型的;分割线控制的是每一个段中每一个row的分割线的类型,它的类型有三种:
typedef NS_ENUM(NSInteger, UITableViewCellSeparatorStyle) {
UITableViewCellSeparatorStyleNone,
UITableViewCellSeparatorStyleSingleLine,
UITableViewCellSeparatorStyleSingleLineEtched
NS_ENUM_DEPRECATED_IOS(2_0, 11_0,
"Use UITableViewCellSeparatorStyleSingleLine for a single line separator.")
}
第一种类型没有分割线,第二种类型有分割线,第三种类型在xcode9.4.1中已经没有使用了,使用第二种来代替。
第一种类型效果
第二种类型效果
然后是tableview中每个section中的row的高度 ,由于有时每个row中的内容是变化的,所以row的高度需要自适应内容的高度
首先我们要估计一个row的高度,就是estimatedRowHeight,然后再设置实际上的rowheight。
背景颜色不用多说,然后后面的两个都是tableView代理属性,接下来会讲到。
三、代理方法
tableview应该至少有两个代理一个是数据源的代理:UITableViewDataSource,另一个是事件处理的代理:UITableViewDelegate。
1、数据源代理及方法实现
在实现数据源的方法之前我们需要有自己的数据,因此建立一个模型SectionGroup,用来配置每个section和每个row中的数据
//sectionGroup.h
#import <Foundation/Foundation.h>
@interface SectionGroup : NSObject
//每个段的头部
@property (nonatomic,copy)NSString * sectionHeader;
//每个段的尾部
@property (nonatomic,copy)NSString * sectionFooter;
//一般出现在右边作为每个section的快速访问
@property (nonatomic,copy)NSString * sectionIndex;
//设置一个装row的数组
@property (nonatomic,strong)NSMutableArray * rowsArray;
+(instancetype)sectionGroup;
@end
//sectionGroup.m
#import "SectionGroup .h"
@implementation SectionGroup
+(instancetype)sectionGroup{
SectionGroup * section = [[SectionGroup alloc]init];
section.sectionHeader = @"这是组的头部";
section.sectionFooter = @"这是组的底部";
section.sectionIndex = [NSString stringWithFormat:@"%d",arc4random_uniform(10)];
section.rowsArray = [NSMutableArray array];
for (int i = 0 ; i < 10; i++) {
//每个section中有10个row
NSString * number = [NSString stringWithFormat:@"这是第%d行",i+1];
[section.rowsArray addObject:number];
}
return section;
}
@end
然后我们在viewcontroller中定义一个数组datalist用来装需要配置的数据,接着我们重写datalist的get方法,因为在点语法点到datalist时,我们需要给相应的对象配置数据。
//懒加载自己的数据
-(NSMutableArray *)dataList{
if (_dataList == nil) {
_dataList = [NSMutableArray array];
for (int i = 0; i<4; i++) {
//datalist里面装section
//一共有4个section
SectionGroup * section = [SectionGroup sectionGroup];
[_dataList addObject:section];
}
}
return _dataList;
}
这样一来,我们就实现了一个类似二维数组的datalist,首先datalist中装的是一个个的section,每一个section中又装的是一个个的row(一个section中是一个rowArray数组),这样一来我们就实现了文章开头那个tableview结构图的数据形式。
然后我们开始着手将这些数据形式通过UI展示出来,首先我们事先数据源代理的两个方法:numberOfSectionsInTableView: 和 numberOfRowsInSection:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
//返回datalist中对象数量,实际就是section的数量
return self.dataList.count;
}
- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//由于无法在当前类中访问section中的rowsArray,所以我们将每一个section取出来
SectionGroup * sections = [self.dataList objectAtIndex:section];
return sections.rowsArray.count;
}
这些设置完毕后,就会得到一个除了header和footer还有分割线之外没有其他任何内容的tableview,因此我们需要去设置每个row对应的cell,再这之前我们需哟啊看一下row和cell的结构关系:
从图中我们可以看出每一个row就是一个cell,且每一个cell都是可以定制的,每一个cell主要包括三个部分,左边的imgaview,中间的textLabel,最右边的accessoryview。
现在我们可以设置cell,需要实现的代理的方法是:cellForRowAtIndexPath:
- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
//使用苹果提供的缓存池机制
//定义一个静态的cell标识符
static NSString * cellID = @"cell";
//首先从缓存池中找标识符为cell的cell
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:cellID];
//如果没有找到,则创建
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID];
}
//先取出对应组,再取出对应行的信息 相当于一个二维数组
SectionGroup * sections = [self.dataList objectAtIndex:indexPath.section];
NSString * numberString = [sections.rowsArray objectAtIndex:indexPath.row];
//设置cell里面的视图
int random = arc4random_uniform(8);
if (random == 2||random == 6) {
//detail只会在cell的类型是subtitl的时候才会显示
cell.detailTextLabel.text = @"我家的后面有一个很大的园,相传叫作百草园。现在是早已并屋子一起卖给朱文公的子孙了,连那最末次的相见也已经隔了七八年,其中似乎确凿只有一些野草;但那时却是我的乐园。";
}else{
cell.detailTextLabel.text = @"说明性的文字";
}
//detailTextLabel是处于textlabel下的一段比较小的文字,一般是对内容的概括
cell.detailTextLabel.numberOfLines = 0;
cell.imageView.image = [UIImage imageNamed:@"timg"];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
//将这个数据显示到对应的cell上
cell.textLabel .text = numberString;
return cell;
}
由于这个方法返回的是一个UITableViewCell,所以我们需要创建一个对应的对象然后返回,但是有一个问题,就是当我们滑动tableview使一个row消失,另一个row出现时,新出现的的row实际上新创建的,这样是很浪费内存的,有没有一种方法可以使新出现的row使用已经消失的row,改变的只是它的内容呢?苹果提供了一种缓存池机制可以实现cell的复用。要使用这种机制,我们需要定义一个静态的标识符(Identifier),然后在创建时使用[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID]方法为新创建的这个cell指定标识符,之后每一次设置cell都优先从当前这个tableview中去找标识符对应的那个cell,然后改变cell的内容即可。
cell设置完毕后,一个简单的tableview视图就算完成了,接下来我们要处理相应的事件了
2、UITableViewDelegate代理方法实现
1、row的删除
对于row的删除需要实现的方法是:editActionsForRowAtIndexPath:
-(NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewRowAction * deleteAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault
title:@"删除"
handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
//删除就是删除掉datalist中的数据,这样在重新加载时就加载新的数据了
[self.dataList removeObjectAtIndex:indexPath.row];
//删除后重新加载表
[tableView reloadData];
}];
NSArray * actions = @[deleteAction];
return actions;
}
由于返回值是一个 UITableViewRowAction的数组,所以我们需要创建UITableViewRowAction,这里初始化时有一个rowActionWithStyle,
typedef NS_ENUM(NSInteger, UITableViewRowActionStyle) {
UITableViewRowActionStyleDefault = 0,
UITableViewRowActionStyleDestructive = UITableViewRowActionStyleDefault,
UITableViewRowActionStyleNormal
} NS_ENUM_AVAILABLE_IOS(8_0) __TVOS_PROHIBITED;
实际上就只有两种类型,normal类型的背景颜色是灰色的,default的背景颜色是红色的。
然后实际上的操作实在block中完成的,实际操作代码这里不做赘述,但需要注意的是一定要在删除后重新加载数据。
最后将这个action装进一个数组中,返回这个数组就行了。
2、row的点击事件的代理方法实现
这个事件需要实现的代理方法是: didSelectRowAtIndexPath:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSString * string = [NSString stringWithFormat:@"第%lu段 第%lu行",indexPath.section,indexPath.row];
//显创建提示框控制器
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"提示" message:string preferredStyle:UIAlertControllerStyleAlert];//样式是alert说明是提示框在中间弹出来,sheet样式是在下面弹出来
//然后创建提示框行为,就是选项
UIAlertAction * sureAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
UIAlertAction * cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
//将行为添加到控制器上面
[alert addAction:sureAction];
[alert addAction:cancelAction];
[self presentViewController:alert animated:YES completion:nil];
}
3、header与footer的设置
需要实现的方法是:titleForHeaderInSection: 和 titleForFooterInSection:
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
SectionGroup * sections = [self.dataList objectAtIndex:section];
return sections.sectionHeader;
}
-(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{
SectionGroup * sections = [self.dataList objectAtIndex:section];
return sections.sectionFooter;
}
我们之前模型SectionGroup只是数据的形式,显示到对应footer和对应的header上需要通过代理来实现。实现的思路都是先取出对应的数据,然后返回给代理。
4、sectionindex的设置
需要实现的方法: sectionIndexTitlesForTableView:
-(NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView{
NSMutableArray * indexArray = [NSMutableArray array];
for (SectionGroup * sections in self.dataList) {
NSString * string = sections.sectionIndex;
[indexArray addObject:string];
}
return indexArray;
}