• 问题总会出现,不过解决问题的方法也会出现!!!

UITableView02-xib

UITableView 小雨 935次浏览 已收录 0个评论

使用xib封装一个view的步骤

1. 新建一个xib文件描述一个view的内部结构(假设叫做MJTgCell.xib)
2. 新建一个自定义的类
(自定义类需要继承自系统自带的view, 继承自哪个类, 取决于xib根对象的Class)
3. 新建类的类名最好跟xib的文件名保持一致(比如类名就叫做MJTgCell)
4. 将xib中的控件 和 自定义类的.m文件 进行连线
5. 提供一个类方法返回一个创建好的自定义view(屏蔽从xib加载的过程)
6. 提供一个模型属性让外界传递模型数据
7. 重写模型属性的setter方法,在这里将模型数据展示到对应的子控件上面

Delegate的使用场合

• 对象A内部发生了一些事情,想通知对象B
• 对象B想监听对象A内部发生了什么事情
• 对象A想在自己的方法内部调用对象B的某个方法,并且对象A不能对对象B有耦合依赖
• 对象A想传递数据给对象B
• ……
以上情况,结果都一样:对象B是对象A的代理(delegate)

使用delegate的步骤

1. 先搞清楚谁是谁的代理(delegate)
2. 定义代理协议,协议名称的命名规范:控件类名 + Delegate
3. 定义代理方法
➢ 代理方法一般都定义为@optional
➢ 代理方法名都以控件名开头
➢ 代理方法至少有1个参数,将控件本身传递出去
4. 设置代理(delegate)对象 (比如myView.delegate = xxxx;)
➢ 代理对象遵守协议
➢ 代理对象实现协议里面该实现的方法
5. 在恰当的时刻调用代理对象(delegate)的代理方法,通知代理发生了什么事情
(在调用之前判断代理是否实现了该代理方法)

通过代码自定义cell(cell的高度不一致)

1.新建一个继承自UITableViewCell的类
2.重写initWithStyle:reuseIdentifier:方法
➢ 添加所有需要显示的子控件(不需要设置子控件的数据和frame, 子控件要添加到contentView中)
➢ 进行子控件一次性的属性设置(有些属性只需要设置一次, 比如字体固定的图片)
3.提供2个模型
➢ 数据模型: 存放文字数据图片数据
➢ frame模型: 存放数据模型所有子控件的framecell的高度
4.cell拥有一个frame模型(不要直接拥有数据模型)
5.重写frame模型属性的setter方法: 在这个方法中设置子控件的显示数据和frame
6.frame模型数据的初始化已经采取懒加载的方式(每一个cell对应的frame模型数据只加载一次)

UITextField

• 通过UITextField的代理方法能够监听键盘最右下角按钮的点击
1. 成为UITextField的代理
self.textField.delegate = self;
2. 遵守UITextFieldDelegate协议,实现代理方法
– (BOOL)textFieldShouldReturn:(UITextField *)textField;
• 在UITextField左边放一个view

self.textField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 0)];
self.textField.leftViewMode = UITextFieldViewModeAlways;

数据源 -> 通过数据源代理 , dataSource , delegate
必须实现的数据源方法:
多少组
多少行
每一行的内容 –> UITableViewCell

cell 的重用

重用标示符
缓存池

cellForRowAtIndexPath:
1. 定义重用标示符
2. 根据重用标示符到缓存池中寻找cell
3. 判断如果cell 找不到, 重新实例化cell
4. 设置并返回

xib中设置重用标识

UITableView02-xib

 

cell被选中的方法

didSelectRowAtIndexPath:

alertStyle: 可以在 alerView上添加textField

/更新数据的步骤

1. 修改数据源中modle的属性
2. 刷新数据
reloadData 刷新所有数据
reloadRowsAtIndexPaths: withRowAnimation:

cell的删除和插入

删除:
1. 删除掉数据源中对应的model
2. 刷新数据
deleteRowsAtIndexPaths: withRowAnimation:

插入:
1. 插入新的model 到数据源
2. 刷新数据

insertRowsAtIndexPaths: withRowAnimation:

// 决定cell的编辑模式
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {

return UITableViewCellEditingStyleInsert;
}

// 点击删除或插入的时候,调用的代理方法
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath

// cell 被选择的时候调用
- (void)tableView:(nonnull UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
NSLog(@"didSelectRowAtIndexPath -- %ld", indexPath.row);
}

// cell 被取消选择的时候调用
- (void)tableView:(nonnull UITableView *)tableView didDeselectRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
NSLog(@"didDeselectRowAtIndexPath -- %ld", indexPath.row);
}

简单团购:

团购实现逻辑

1. Cell的实现
基本cell无法满足要求: 只有 textLabel, imageView, detailTextLabel
虽然基本控件满足需要, 但是显示却无法满足需要, 因此需要自定义

使用xib 进行界面描述
1. 新建xib文件进行UI界面描述
2. 创建一个类文件和xib文件进行关联
3. 为类文件创建model 属性, 方便对内部属性进行赋值
4. 在数据源方法中 cellForRowAtIndexPath: 返回cell的方法里, 如果cell 为nil, 就从xib中加载加载cell

2. 通过xib/代码 自定义footerView
在自定义footerView的时候,不能直接为tableView的footerView进行赋值,那么就无法对他进行调整.一般是这么做的:
1. 创建一个tempView 直接赋值给footerView
2. 创建一个Button添加到tempView上(本案例需要一个按钮),这样就可以对Button进行调整
3. 使用代理的方式传递footerView中按钮被点击了
4. 点击按钮进行加载更多的时候
> 加载更多的页面使用单独的view进行封装:包括菊花,正在努力加载…文本
> 延迟一段时间进行刷新tableView中cell的数据使用GCD来实现 dispatch_after
> 加载完成之后,tableView要滚动到最新的一行 scrollToRowAtIndexPath:

3. headerView (封装scrollView去实现)
单独创建一个scrollView来进行实现
1. 创建一个类继承自UIView
2. 内部添加scrollView和UIPageControl
3. 根据传递过来的数据进行设置
4. 如果有点击, 可以通过代理进行实现

通过xib 自定义cell

自定义cell的时候, 需要在属性中设置 identifier

为什么要自定义cell?

系统内置的cell 无法满足需求

通过xib自定义cell:

只需要在xib 文件中, 拖放一个 UITableViewCell控件, 修改需要的宽高
xib 只是帮我们实现了界面的布局

如果想对内部的子控件进行赋值, 就需要和类(继承自UITablViewCell)结合起来使用

为自定cell中 控件进行赋值:

为cell 定义了一个model 属性 (cell需要的数据都在modle中)

@property (nonatomic, strong) GrouponModel *grouponModel;

在设置cell的时候, 直接使用 : cell.grouponModel = model;

在cell类内部要重写 set方法

- (void)setGrouponModel:(GrouponModel *)grouponModel {
// 1. 对属性进行赋值
重写了set方法, 就要手动的对 _grouponModel进行赋值
可以保证在其他的方法中引用到 grouponModel

// 2. 对控件进行赋值
}

自定义bannerView

1. 这个view 是干什么的?
初始化的一些设置
bannerView 是用来在scrollView上显示多张图片, pageControll

2. 这个view 需要什么数据?
设置view中控件需要的数据
bannerView需要图片名称
自定义一个实例化方法, 同时接收frame 和 装着图片名称的数组

- (instancetype)initWithFrame:(CGRect)frame andImageArray:(NSArray *)imageArray

内部进行实例化的时候, 是把 初始化UI界面, 和 数据设置进行分离

对外提供了一个 属性

@property (nonatomic, strong) NSArray *imageArray

在实现文件中, 重写set方法
那么在对内部子控件进行赋值的时候, 就直接 调用 数据设置的方法

 

3. 这个view 需要对外提供什么?

对外开发一些接口(返回值, 调用方法后做一些事情)
bannerView如果需要点击imageView做一些事情, 就需要提供代理方法

设置footerView注意事项:

设置的时候, 这些无效的属性是因为tableView会拿到这个被设置的view做一些操作, 会导致有些参数赋值并没有产生效果
/**
x 有效
y 无效
width 无效
height 有效
*/

解决办法:

创建一个临时的view, tempView

// 对于tablView来说, 会修改 tempView
_tableView.tableFooterView = tempView;

如果想要实现自定义的布局, 就创建一个view, InnerView, 并添加到 tempView
tableView并不会对 tempView的子view进行修改

xib自定义footerView
footerView 作用?
点击加载更多 — > 点击按钮之后, 显示出正在加载的view

点击加载更多按钮

1. 让loadMoreView 显示出来
2. 加载更多数据

这两件事情应该是由控制器来做, 所以使用代理通知控制器

footerView 对外提供了一个方法

// 提供给外部用来控制, loadMoreView的显示或隐藏
- (void)showLoadMoreViewWith:(BOOL)isShow

// 通过GCD的方式, 来实现延迟执行代码
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
}

简单微博:

微博实现逻辑

首先: 微博里cell的不同样式,就需要自定义cell
其次: 跟团购比起来不同的是, 没一行cell的高度是不一样的, 因为显示的内容不一样

在设置tableView的时候需要清楚的知道每一行cell的高度, 查看代理方法的调用顺序,talbeView在加载的时候:
1. 首先是计算所有行的高度,加载了多少数据就会计算多少次, 也就是说装载数据的数组里有多少个元素就计算多少次
2. 然后再去通过当前屏幕显示多少行,再返回具体行的高度

也就是说, 首先要先计算每一行cell的高度, 在heightForRowAtIndexPath: 这个方法里返回. 计算的时候需要注意的是:
1. 宽度是一定的, 屏幕就那么宽
2. 高度是不一定的情况
> 微博里是否有配图
> 微博里文字的长短
需要创建一个类(CellFrame)来计算cell的高度,根据我们之前写代码的需求, 数据也需要写一个model,(ContentModel)来进行描述
但是两个模型是有一定关联的, 因为 CellFrame 这个计算cell高度的类需要 具体的数据才能进行计算,数据是放在ContentModel里的

需要注意的是:

> 既然CellFrame 是用来计算cell高度的, 那么高度就是cell里面每一个子控件的高度累积起来的总高度,也就是说也需要知道其他子控件的frame,所以,这个类就用来计算所有控件的frame以及cell的高度

> cell里所有的子控件都是在cell的contentView上的,当使用 CellFrame 用来计算frame的时候, 那么在cell里就只需要对控件进行实例化, 然后添加到contentView就可以了, 在进行赋值的时候使用CellFrame, 对内部子控件的frame进行赋值同时赋值(因为CellFrame中有ContentModel属性)

在进行计算的时候, 最主要的就是对文本size的计算: boundingRectWithSize: 限定宽度, 高度不限定进行设置

注意点:

[text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:dict context:nil];

maxSize : 是指限制的一个范围, 而不是实际的宽高, 计算text的size 最大就这么大
options : NSStringDrawingUsesLineFragmentOrigin : 需要使用这种options才会起作用
attributes : 使用字典存放的文本属性

> 用户的name的长度是不一定的, 那么在进行计算的时候, 我们只需要限定名字的高度, 宽度不进行限定就可以了, 针对用户的长度设置, 可以在用户注册的时候进行限制.
> 在显示name的Label进行初始化的时候, 需要设置Label中显示的字体大小, Label文本默认显示的文本大小是17,如果在计算文本设置属性的时候文本大小不是17,那么结果就会有偏差

1. 每个cell 上显示的内容不一样

自定义cell 实现cell 上显示内容

2. 每一个cell的高度都不一样

_tableView.rowHeight = 60 -> 只适用于所有的cell 高度都一样

需要实现

heightForRowAtIndexPath: -> 为每个cell设置 适合的高度

// 向tableView 注册, 重用标示符, 和对应的class
使用这个方法进行注册之后,

[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kIdentifire];

进行cell 为 nil 的判断代码就不用写

// 3. 做判断

if (nil == cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}

tableView 数据源方法调用的顺序:
1. numberOfSectionsInTableView
2. numberOfRowsInSection:
3. heightForRowAtIndexPath:
数据源中有多少数据, 就调用多少次
为什么会调用那么多次?
确定contentSize, 可以滚动的范围

4. 调用 cellForRowAtIndexPath: 然后调用 heightForRowAtIndexPath

双模型的设计:

weiboModel : 把字典转为模型
weiboFrameModel : 通过 传递过来的 weiboModel 来计算每一个控件的frame以及cell的高度
定义的数组里最终存放的是: weiboFrameModel

只需要为cell 赋值 weiboFrameModel (包含了frame和weiboModel)

// textMaxSize 文本能够显示的最大范围
// @{NSFontAttributeName:[UIFont systemFontOfSize:17]} : 在计算文本实际大小的时候, 需要传入字体大小

[weiboModel.text boundingRectWithSize:textMaxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17]} context:nil].size;

// 向tableView 注册, 重用标示符, 和对应的class , 针对自定义cell的时候
[self.tableView registerClass:[WeiboCell class] forCellReuseIdentifier:kIdentifire];

在返回cell的时候, 进行的空判断就可以省略掉


本博客内容既有转载自网络的内容,也有本作者原创内容,仅供学习与交流之用
如有侵权或者错误之处,请及时在下方留言!
喜欢 (1)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址