IOS-UITableView 及自定义cell
- 类似于Android中的ListView,继承自UIScrollView。
- 分为两种样式
- UITableViewStylePlain (每组数据之间不留空隙)
- UITableViewStyleGrouped (每组数据之间间隔明显)
- Plain样式有一个特点:
- 在展示数据时,不同组的头部标题会在显示着一组数据时停留在顶部
- 这样可以起到提示当前显示哪一组的作用
- 我们还可以自定义展示UITableView的HeaderView和FooterView
- 并且还可以在其代理中自定义每一组的HeaderView和FooterView
- 如何展示数据
- 如果使用UITableView来展示数据,
- 那么这个对象必须遵守UITableViewDataSource协议
- 并且成为UITableView的数据源
- UITableViewDataSource协议的方法
// 一共有多少组数据- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;// 每一组有多少行数据- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;// 每一行显示什么内容- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;//每组数据显示怎样的头部和尾部标题- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
数据刷新
- [taleView reloadData]; //重新加载全部数据
- [tableView reloadRowAtIndexPaths: withRowAnimation:] //刷新某一行
NSIndexPath对象内包含section和row两个属性
- UITableViewCell
- UITableView的每一行都是一个UITableViewCell,通过dataSource的tableView:cellForRowAtIndexPath:方法来初始化每一行
- 通过选择UITableViewCell不同的样式,我们可以在UITableViewCell内显示不同的内容
- UITableViewCell内部有三个属性
- UIImageView* imageView
- UILabel* textLabel
- UILabel* detailTextLabel
- UIImageView* imageView
- UITableViewCell内部有三个属性
UITableViewCell的样式有:
T UITableViewd的样式 - UITableView的每一行都是一个UITableViewCell,通过dataSource的tableView:cellForRowAtIndexPath:方法来初始化每一行
UITableViewCell内部有个默认的子视图:contentView,
- contentView是UITableViewCell所显示内容的父视图,可显示一些辅助指示视图
- 辅助指示视图的作用是显示一个表示动作的图标,可以通过设置UITableViewCell的accessoryType来显示,默认是UITableViewCellAccessoryNone
- 其他还有
- UITableViewCellAccessoryDisclosureIndicator
- UITableViewCellAccessoryDetailDisclosureButton
- UITableViewCellAccessoryCheckmark
- 还可以通过cell的accessoryView属性来自定义辅助指示视图(比如往右边放一个开关)
- cell的重用
- 类似与Android中的ListView的优化问题,IOS中UITableView的优化类似
- ios的cell缓存机制
- 当滚动列表时,部分UITableViewCell会移出窗口,UITableView会将窗口外的UITableViewCell放入一个对象池中,
等待重用。当UITableView要求dataSource返回UITableViewCell时,dataSource会先查看这个对象池,如果池中有未使用的UITableViewCell,
dataSource会用新的数据配置这个UITableViewCell,然后返回给UITableView,重新显示到窗口中,从而避免创建新对象
- 当滚动列表时,部分UITableViewCell会移出窗口,UITableView会将窗口外的UITableViewCell放入一个对象池中,
- 简单的cell重用代码如下
- 类似与Android中的ListView的优化问题,IOS中UITableView的优化类似
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{// 1.定义一个cell的标识static NSString *ID = @"mjcell";// 2.从缓存池中取出cell (其实系统维护了一个cell缓存链表)UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];// 3.如果缓存池中没有cellif (cell == nil) {cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];}// 4.设置cell的属性...return cell;}
storyboard中的静态cell
- 对于app中的cell,有的cell可能就是用来显示固定数据的
- 这些cell的数据并不需要动态的设置,比如一个设置界面的选项布局
- xcode允许我们直接在storyboard中描述这种cell, 这样我们就可以不用实现数据源相关方法了。
- 注意的是:你如果实现了数据源相关方法,就不可能使用静态单元格成功!!!
- xcode在从storyboard中加载tableView时,会直接把我们描述的静态cell给显示在TableView上
- 对于TableView的content选择 static cell
- 对于每一种静态cell的布局,我们有两种方式选择,根据 TableViewCell的style属性
- 使用系统自带的4个样式
- 选择custom, 这样对于每个静态cell我们可以自己进行布局(拖控件,设置控件内容)
- 小技巧: 一般先搞定一组,然后在改变section属性时,Interface Builder会自动帮我们按已经搞定的一组复制出其他几组
cell的编辑
- IOS中的TableView自带两种编辑状态:添加和删除, 默认进入编辑状态是删除状态
- 删除状态:自带的侧滑删除的效果
- 添加状态:可以添加数据
- 相关方法
- IOS中的TableView自带两种编辑状态:添加和删除, 默认进入编辑状态是删除状态
// 让tableView进入编辑状态的两种方式[tableView setEditing: YES animated:YES];tableView.editing = YES;#pragma mark - tableView的代理方法/*** 当tableView进入编辑状态的时候会调用,询问每一行进行怎样的操作(添加\删除)*/- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{//TODO:返回要进行的操作类型}/*** 如果实现了这个方法,就自动实现了添加和滑动删除的功能* 提交了一个编辑操作就会调用(操作:删除\添加)这个方法* @param editingStyle 编辑的行为* @param indexPath 操作的行号*/- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{if (editingStyle == UITableViewCellEditingStyleDelete) { // 提交的是删除操作//TODO: 更改数据模型,刷新表格} else if (editingStyle == UITableViewCellEditingStyleInsert) {//TODO: 更改数据模型,刷新表格}}
- 如果对展示的数据进行了编辑操作, 那么我们就必须要刷新表格,那么怎么刷新才是比较有效率的呢?
- [self.tableView reloadData];
- 全局刷新, 但性能不好
- [tableView deleteRowsAtIndexPaths:(NSArray*)deleteRows withRowAnimation:];
- 这个方法使用前提是:删除后的行数应和修改后的数据模型中数据的个数相同
- 这个方法调用后,会刷新被删除的行后面cell,并不会向前刷新
- [tableView insertRowsAtIndexPaths:(NSArray*)insertRows withRowAnimation:];
- 类似与上面的方法,也只是会刷新后面的cell
- [tableView reloadRowsAtIndexPaths:(NSArray*)Rows withRowAnimation:]
- 这个方法用来刷新指定行,要求数据模型内数据的个数不能变。
- 自定义cell
- 一共有两种方式,一种是通过xib,一种是通过代码
- xib适合定义样式相同的cell
- 通过代码一般定义样式不同的cell
- 通过xib
- 新建一个xib文件,并拖出一个Table View Cell
- 样式设置为 Custom, 并且定义好标识以便重用
- 摆放自己需要的控件
- 定义一个类,描述这个Cell, 并把这个Cell对应的类设置为我们自定义的类
//描述xib中自定义cell的类//这里只看快速创建cell的类方法@implementation MJTGCell+ (instancetype)cellWithTableView:(UITableView *)tableView{static NSString *ID = @"cell"; //这个标识与xib文件中,cell的标识相同// dequeueReusableCellWithIdentifier 先从缓存池中取,如果缓存池中没有可循环利用的cell,先去storyboard中找到合适的cell(静态cell)// cell是从storyboard中创建出来的MJTGCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; if (cell == nil) {//获取xib文件中我们描述的Table View Cell, 并会把这个cell以在xib文件中定义标识为准,丢入缓存池中cell = [[[NSBundle mainBundle] loadNibNamed:@"MJTGCell" owner:nil options:nil] lastObject];}return cell;}//如果cell是通过storyboard或者xib创建的,就会调用这个方法来初始化cell- (void)awakeFromNib{// Initialization codeUIView *divider = [[UIView alloc] init];divider.backgroundColor = [UIColor blackColor];divider.alpha = 0.2;[self.contentView addSubview:divider];self.divider = divider;}@end
- 通过代码
- 对于通过代码自定义的cell,一般每个cell的样式可能都不相同
- 即其中基本控件相同,但控件大小随数据的变化而变化,即frame不固定
- 这些控件的frame是在数据决定后才决定的
- 经过上面的分析,对于这种自定义cell,我们提供两个模型(把data模型算上的话是3个)
- 自定义cell模型,这个模型中定义了cell中含有的基本控件的样式和属性
- FrameData模型,这个模型可以根据data模型,算出data展示时所需要的控件的大小
- 给cell模型设置FrameData模型就可以使cell变的完整可用,即cell的展示是根据数据来动态变化的
- 需要注意: 自定义的cell继承自UITableViewCell, 并且自定义的控件都要添加到cell的contentView中!!!!
- 范例如下
- 该范例展示的自定义cell中含有一个UIImageView,一个UILabel和一个UIButton
- 核心方法如下
/***自定义cell:MJMessageCell*/@implementation MJMessageCell //继承自UITableViewCell//类构造方法,快速创建cell+ (instancetype)cellWithTableView:(UITableView *)tableView{static NSString *ID = @"message";MJMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];if (cell == nil) {cell = [[MJMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];}return cell;}//重写initWithStyle:reuseIdentifier: 以便cell的可重用- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];if (self) {//TODO, 定义cell内基本控件的属性// 这里需要初识化的控件有有UIImageView 和两个UILabel//这些控件都要添加到cell的contentView中!!!!!!!!}return self;}- (void)setMessageFrame:(MJMessageFrame *)messageFrame{//在这个方法中,根据传入的FrameData模型,设置控件相关的frame}//FrameData模型:@interface MJMessageFrame : NSObject//各种对应cell中控件的Frame@property (nonatomic, assign, readonly) CGRect iconF;@property (nonatomic, assign, readonly) CGRect timeF;@property (nonatomic, assign, readonly) CGRect textF;@property (nonatomic, assign, readonly) CGFloat cellHeight;//数据模型@property (nonatomic, strong) MJMessage *message;@end@implementation MJMessageFrame- (void)setMessage:(MJMessage *)message{//TODO: 根据数据设置相关的frame属性}//在控制器中,FrameData模型作为数据源@interface MJViewController () <UITableViewDataSource, UITableViewDelegate>@property (weak, nonatomic) IBOutlet UITableView *tableView;@property (nonatomic, strong) NSMutableArray *messageFrames; //FrameData模型@end@implementation MJViewController//懒加载FrameData模型- (NSMutableArray *)messageFrames{if (_messageFrames == nil) {//TODO, 加载数据模型,并将数据模型设置给FrameData模型}_messageFrames = mfArray;}return _messageFrames;}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{// 1.创建cellMJMessageCell *cell = [MJMessageCell cellWithTableView:tableView];// 2.给cell传递模型cell.messageFrame = self.messageFrames[indexPath.row];// 3.返回cellreturn cell;}//设置每个cell的高度- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{MJMessageFrame *mf = self.messageFrames[indexPath.row];return mf.cellHeight;}@end