# 一.目录
此为第一部分:
### 1. – UITableView的基本使用方式
### 2. – UITableView的两种显示方式
### 3. – UITableView之所以能滚动的原因
### 4. – 3个数据源方法
### 5. – 设置行高
### 6. – UITableView的常见属性
### 7. – UITableViewCell的常见属性
### 8. – UITableViewCell的重用
### 9. – 监听UITableView行的选中事件
# 二. 解析
### 1. – UITableView的基本使用方式
**1.1UITableView:整体上来说,就是一个表格控件,在ios的实际开发过程中, UITableView的使用相当广泛,是非常重要的一个控件,所以一定要引起重视,特别是对于新手来说;**
**1.2. UITableView就是表格控件**
– UITableView由行和列来组成
– UITableView中每行只有1列
– 每行中保存的都是一个UITableViewCell对象
**1.3. UITableView一般用来展示表格数据、可以滚动(继承自UIScrollView)、性能极佳**
如果没有UITableView, 实现类似的功能只能自己通过循环创建控件, 性能差
### 2. – UITableView的两种显示方式
1> Plain, 简明样式(不分组的样式)
2> Grouped, 分组的样式
– 无论分组样式还是不分组样式, 其实都能显示分组数据、显示组标题、组描述。
设置方法: Main.storyboard—>选择控件UITableView —>属性 —>Style—> Plain / Grouped
### 3. – UITableView之所以能滚动的原因
从继承关系上来说,UITableView继承自UIScrollView,这是它之所以能滚动的根本原因, 如下图:
### 4. –UITableView的 3个数据源方法
说到UITableView,首先我们需要知道的就是,UITableView有两个必须要实现的数据源方法, 和多个可选实现的数据源方法
我们先来看UITableView的数据源方法底层
–UITableViewDataSource 的底层实现:
// this protocol represents the data model object. as such, it supplies no information about appearance (including the cells)
@protocol UITableViewDataSource<NSObject>
@required
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
@optional
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; // Default is 1 if not implemented
从UITableViewDataSource数据源方法的底层,我们可以看出,有两个数据源方法,是我们必须要实现的,那就是:返回行,和返回cell的方法:如下
// 返回对应组有多少行
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
// 根据索引,返回每组每行的单元格cell到底长什么样
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
而对于,返回组的数量的数据源方法来说,是可选实现的方法, 默认不实现的情况下,表示组数为1;如果有多个组,就必须要有这个方法:
// 返回有多少组
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
注意:在实现数据源方法之前,先要找数据源方法的数据源代理对象,一般我们都找当前控制器:
- (void)viewDidLoad {
[super viewDidLoad];
//设置控制器为数据源对象
self.tableView.dataSource = self;
}
然后让当前控制器遵守数据源协议:
@interface ViewController ()<UITableViewDataSource>
补充: UITableView组标题和组描述的数据源方法:
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; // fixed font style. use custom view (UILabel) if you want something different //组标题
- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section; //组描述
### 5. – 设置行高
对于设置行高,一般情况我们有两种,一种是统一设置行高,一种是预设行高,
统一设置行高: 主要用于cell行高都是一样的情况下
- (void)viewDidLoad {
[super viewDidLoad];
//设置控制器为数据源对象
self.tableView.dataSource = self;
//设置统一的行高
self.tableView.rowHeight = 70;
}
预设行高: 主要用于动态计算cell行高的情况下:
- (void)viewDidLoad {
[super viewDidLoad];
//设置控制器为数据源对象
self.tableView.dataSource = self;
//预设行高
self.tableView.estimatedRowHeight = 300;
}
设置预估行高的意义?
设置预估行高,可以让程序加载效率高很多,一开始的时候只显示首屏的几行,而不是显示所有的行数,只有需要的时候再加载调用; 预估行高越接近实际行高,越好,这样性能越高!
### 6. – UITableView的常见属性
基本属性解读:
右侧属性栏中属性分为三大类属性:1、Table View 2、Scroll View 3、 View
下面只看Table View中的属性:
Content: Dynamic Prototypes 动态单元格
Static Cells 静态单元格
Prototype: Cells cell的行数
Style 样式 : Plain 简单样式(未分组)
Grouped 分组样式
separator : 分隔线属性
selection : 单前选中行
background : 背景色
上面是在Main.storyboard中的展示,而众多的属性,都是有源代码的,这些也可以在UITableView的头文件中查看,
下面是我摘录的部分UITableView的底层源代码: (部分)
NS_CLASS_AVAILABLE_IOS(2_0) @interface UITableView : UIScrollView <NSCoding>
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER; // must specify style at creation. -initWithFrame: calls this with UITableViewStylePlain
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly) UITableViewStyle style;
@property (nonatomic, weak, nullable) id <UITableViewDataSource> dataSource;
@property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;
@property (nonatomic) CGFloat rowHeight; // will return the default value if unset
@property (nonatomic) CGFloat sectionHeaderHeight; // will return the default value if unset
@property (nonatomic) CGFloat sectionFooterHeight; // will return the default value if unset
@property (nonatomic) CGFloat estimatedRowHeight NS_AVAILABLE_IOS(7_0); // default is 0, which means there is no estimate
@property (nonatomic) CGFloat estimatedSectionHeaderHeight NS_AVAILABLE_IOS(7_0); // default is 0, which means there is no estimate
@property (nonatomic) CGFloat estimatedSectionFooterHeight NS_AVAILABLE_IOS(7_0); // default is 0, which means there is no estimate
@property (nonatomic) UIEdgeInsets separatorInset NS_AVAILABLE_IOS(7_0) UI_APPEARANCE_SELECTOR; // allows customization of the frame of cell separators
@property (nonatomic, strong, nullable) UIView *backgroundView NS_AVAILABLE_IOS(3_2); // the background view will be automatically resized to track the size of the table view. this will be placed as a subview of the table view behind all cells and headers/footers. default may be non-nil for some devices.
源文件前半部分代码如上,我们可以看到,越靠前的属性越为重要:
第6行 UITableViewStyle 枚举类型的 style 属性,是用于设置UITableView的style样式,源文件中枚举类型如下:
typedef NS_ENUM(NSInteger, UITableViewStyle) {
UITableViewStylePlain, // regular table view
UITableViewStyleGrouped // preferences style table view
};
通过查看底层,我们可以看出,越是靠前的属性,就越是重要,
可以看出tableView的样式和工具栏上的一样是两种,Plain和Grouped。
第7行是提供了一个设置数据源方法的属性 上面已经有介绍了
第8行提供了一个tableView代理方法的属性
第9行为row的高度,也就是行高
第10行为头View高度 —组标题
第11行为尾部View高度 —组描述
>>>UITableView的部分常见属性解析:
* rowHeight, 可以统一设置所有行的高度 在viewdidload 里面
如果每行的行高是一致的,就不要使用代理方法,直接设置UITableView的rowHeight属性即可,这样高效!
见上面第6;
* separatorColor, 分隔线的颜色
* separatorStyle, 分割线的样式
//设置每行之间分割线的颜色和样式
self.tableView.separatorColor = [UIColor redColor];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
* tableHeaderView, 一般可以放广告 是一个UIView类型 在最上面 也就是头部
* tableFooterView, 一般可以放加载更多 在最下面,也就是尾部
//设置tableview的header
UIView *hearderVw = [[UIView alloc]init];
hearderVw.backgroundColor = [UIColor redColor];
hearderVw.alpha = 0.5;
//当一个控件要作为tableview的header view来显示的时候,此时设置的x,y,w都无效,只有高h有效
hearderVw.frame = CGRectMake(0, 0, 0, 150);
self.tableView.tableHeaderView = hearderVw;
//设置tableview的footer view
UIView *footerVw = [[UIView alloc]init];
footerVw.backgroundColor = [UIColor blueColor];
//当一个控件要作为tableview的footer view来显示的时候,此时设置的y,w都无效,只有高h和x有效
footerVw.frame = CGRectMake(10, 0, 0, 200);
self.tableView.tableFooterView = footerVw;
### 7. – UITableViewCell的常见属性
对于UITableViewCell来说,它的属性非常多,我这里列举几个最常用的:
源代码底层如下:
NS_CLASS_AVAILABLE_IOS(2_0) @interface UITableViewCell : UIView <NSCoding, UIGestureRecognizerDelegate>
// Designated initializer. If the cell can be reused, you must pass in a reuse identifier. You should use the same reuse identifier for all cells of the same form.
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier NS_AVAILABLE_IOS(3_0) NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
// Content. These properties provide direct access to the internal label and image views used by the table view cell. These should be used instead of the content properties below.
@property (nonatomic, readonly, strong, nullable) UIImageView *imageView NS_AVAILABLE_IOS(3_0); // default is nil. image view will be created if necessary.
@property (nonatomic, readonly, strong, nullable) UILabel *textLabel NS_AVAILABLE_IOS(3_0); // default is nil. label will be created if necessary.
@property (nonatomic, readonly, strong, nullable) UILabel *detailTextLabel NS_AVAILABLE_IOS(3_0); // default is nil. label will be created if necessary (and the current style supports a detail label).
// If you want to customize cells by simply adding additional views, you should add them to the content view so they will be positioned appropriately as the cell transitions into and out of editing mode.
@property (nonatomic, readonly, strong) UIView *contentView;
// Default is nil for cells in UITableViewStylePlain, and non-nil for UITableViewStyleGrouped. The 'backgroundView' will be added as a subview behind all other views.
@property (nonatomic, strong, nullable) UIView *backgroundView;
UITableViewCell的常见属性:
* imageView 图片框 如头像 都是放在Cell contentView 里面的
* textLabel 上面的label 如英雄名字 都是放在Cell contentView 里面的
* detailTextLabel 下面的label 如英雄介绍
* accessoryType 右侧指示器 这是一个枚举enmu集合.里面有好几种样式,如小箭头
* accessoryView 这是一个view类型,可以给任何的空间,如开关
* backgroundColor , 设置单元格的背景颜色
* backgroundView, 可以利用这个属性来设置单元格的背景图片, 指定一个UIImageView就可以了。
* selectedBackgroundView , 当某行被选中的时候的背景。选中之后显示的效果,
UITableView的每一行都是一个UITableViewCell,通过dataSource的tableView:cellForRowAtIndexPath:方法来初始化每一行
UITableViewCell内部有个默认的子视图:contentView,contentView是UITableViewCell所显示内容的父视图,可显示一些辅助指示视图 见第14行代码
辅助指示视图的作用是显示一个表示动作的图标,可以通过设置UITableViewCell的accessoryType来显示,默认是UITableViewCellAccessoryNone(不显示辅助指示视图),其他值如下:
contentView下默认有3个子视图
其中2个是UILabel(通过UITableViewCell的textLabel和detailTextLabel属性访问) 见第10 ,11行代码
第3个是UIImageView(通过UITableViewCell的imageView属性访问)
UITableViewCell还有一个UITableViewCellStyle属性,用于决定使用contentView的哪些子视图,以及这些子视图在contentView中的位置
### 8. – UITableViewCell的重用
iOS设备的内存有限,如果用UITableView显示成千上万条数据,就需要成千上万个UITableViewCell对象的话,那将会耗尽iOS设备的内存。要解决该问题,需要重用UITableViewCell对象
重用原理:当滚动列表时,部分UITableViewCell会移出窗口,UITableView会将窗口外的UITableViewCell放入一个对象池中,等待重用。当UITableView要求dataSource返回UITableViewCell时,dataSource会先查看这个对象池,如果池中有未使用的UITableViewCell,dataSource会用新的数据配置这个UITableViewCell,然后返回给UITableView,重新显示到窗口中,从而避免创建新对象
* 性能优化步骤:
1> 通过一个标识符ID去缓存池中查找是否有对应的cell
2> 如果有则取出来使用, 如果没有, 则创建一个。
3> 设置cell数据
4> 代码实现重用cell功能。
5> 优化cell_id变量。(注意标识命名要规范)
下面我总结一下用代码创建cell的三种方式,重用ID的方式,是后面第2,3两种方法,推荐使用第3个方法!
@implementation ViewController
static NSString *ID = @"cell_id”;
//这里要申明一下ID,也就是缓存池创建的cell的标记符 在ViewController里面申明这个ID是什么!
//添加static 关键字, 这样创建的ID会转移保存到静态区,这样每次创建的ID和 常量”cell_id” 都不会被销毁, 下一次创建的时候 直接调用,不会重复创建了;
//2.创建一个单元格 有三种方式:如下 推荐使用第三种;
// 2.1> 先从缓存池中尝试去取一个单元格
// 用来标识缓存池中的cell的id (可重用id)
// static NSString *ID = @"cell_id";
// 根据可重用ID, 在缓存池中查找对应的cell
//方式1 :原始方式 方式1是每移除一个,就创建一个cell,会不停的创建,性能低,总有内存爆满的时候,会崩溃
UITableViewCell *cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
//方式2: 如刚好一个 页面有8个cell,一启动会创建8个,当滚动一点点的时候,第一个没有进缓存池,而第9个已经出来了,这个时候又多了一个cell,这种方式至少会创建9个,如果是ABAB型的,还会有第10个,也有点不妥,
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
//方式3:这种方式是最好的,如,也是ABAB类型的,到缓存池里面找和创建cell是一步搞定,底层是直接就写好了的, 但注意,这里需要在启动程序的时候注册一个cell,也就是在viewDidLoad里面, 推荐用这种!!
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID forIndexPath:indexPath];
//此时,当一个cell从屏幕滚出去以后,不会销毁,而是放到了"缓存池"中;
#pragma mark ----viewDidLoad 设置数据源对象
- (void)viewDidLoad {
[super viewDidLoad];
//设置控制器为数据源对象
self.tableView.dataSource = self;
//设置统一的行高
self.tableView.rowHeight = 70;
//注册一个cell
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:ID];
}
——>
这个是为方法3注册的一个cell,便于方式3调用; 要用这种方法,必须要先注册一个cell 在viewDidload 里面注册,注册就是告诉系统,我们用的ID到底是什么类型的cell,
### 9. – 监听UITableView行的选中事件
在UITableView中,我们时常需要监听cell的事件,来实现一些我们自定义的属性和方法,那么怎样监听每个cell的点击事件 呢?
这里我列举一些我们常用的监听方法:
1>* 通过代理来监听, 可以使用于cell的任何一种监听场景
很重要!!! 我这里主要列举两个非常常用的方法,更多的方法,大家可以在UITableViewDelegate方法中查看
** 选中某行: – (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
** 取消选中某行: – (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
——> 注意,这个方法名只有细微的差别,千万不要错了
代码底层:
// Called after the user changes the selection.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(3_0);
2> 弹出UIAlertView、UIAlertController 来监听,
这个方法主要适用场景是:用于在点击某一行cell,需要跳转,或者增,删,改,的时候
* 修改弹出对话框的样式
alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
* 根据索引获取指定的某个文本框
[alertView textFieldAtIndex:0];
e.g:
[alertView textFieldAtIndex:0].text = hero.name;
* 通过UIAlertView的代理来监听对话框中的按钮的点击事件
* 实现UIAlertView的 – (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex代理方法
UIAlertViewDelegate 代理方法底层:
@protocol UIAlertViewDelegate <NSObject>@optional
// Called when a button is clicked. The view will be automatically dismissed after this call returns
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex NS_DEPRECATED_IOS(2_0, 9_0);
====>> 代码实现:
DXTableViewCell.h
#import <UIKit/UIKit.h>
@interface HMTableViewCell : UITableViewCell
@end
// DXTableViewCell.m
#import "HMTableViewCell.h"
@implementation HMTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
return [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier];
}
@end
这里重定义的类是为了,来刷新数据的时候,让cell始终处于一种状态:UITableViewCellStyleSubtitle,这样定义之后,物理注册的时候cell是什么状态,最后都会返回这个状态;
didSelectRowAtIndexPath 选中某行做什么事情, 很重要!!! 里面可以写任何操作方式
--------------------------------------------------
@interface ViewController () <UITableViewDataSource, UITableViewDelegate, UIAlertViewDelegate> //同时让控制器要继承UIAlertViewDelegate这个协议
// 记录当前选中行 这里需要先定义一个属性,让下面的代理方法的索引选中的行,赋值给它,便于局部刷新的时候可以调用
@property (nonatomic, strong) NSIndexPath *idxPath;
--------------------------------------------------
#pragma mark - table view的代理方法
// 当用户选中某个的时候会触发这个事件(会执行这个方法)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.idxPath = indexPath; //把当前选中的行赋值给这个属性
// 立刻取消某行的选中效果(取消选中)
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// 0. 获取当前选中行的模型数据
HMHero *hero = self.heros[indexPath.row];
// 1. 弹出一个UIAlertView
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"编辑" message:nil delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
// 1.1修改alert view的样式, 让alertView中有一个文本框
alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
// 1.2获取alert view中的文本框
UITextField *txtField = [alertView textFieldAtIndex:0];
// 1.3把英雄的名字设置到文本框中
txtField.text = hero.name;
//2.显示出来
[alertView show];
}