一 运行图和解析顺序
1 运行图:
2 解析顺序:
—-> 2.1 新增的刷新功能(点击各自标题刷新和点击tabBar中的按钮刷新对应的内容)
—-> 2.2 论cell的2种做法
—-> 2.3 自定义cell
—-> 2.4 设置cell的数据
—-> 2.5 计算cell的高度
—-> 2.6 处理热门评论
二 新增的数据刷新功能(接上一篇)
1 新增原因: 目前市面上很多app都有一样的功能,就是当程序启动进入主界面的时候,数据会刷新.当用户点击该页面对应的title标题的时候又会刷新页面;同时当用户点击该页面对应的控制器中的按钮的时候,也会刷新数据.这就是现在很对app都具有的功能.
三 底部tabBar按钮刷新功能
1 监听按钮的点击事件—>两种方法
—-> 1.1 用addTag监听: 由于tabBarButton是继承UIControl,那么可以用addTag来监听.
—-> 1.2 用代理方法: 这个tabBarButton比较特别,其内部已经准守了协议,只需要我们重写代理的方法就可以.
2 步骤:
—-> 2.1 由于是底部的tabBar按钮,那么我们来到有关tabBar按钮设置的文件
—-> 2.2 对按钮的监听
//对按钮的监听
[button addTarget:self action:@selector(ButtonClickRefresh:) forControlEvents:UIControlEventTouchUpInside];
—-> 2.3 实现监听方法(内部采用发布通知的方法实现对按钮的监听,只要有点击按钮就会发布通知)
#pragma mark - 实现监听的方法
- (void)ButtonClickRefresh:(UIControl *)button
{
//判断当前按钮是不是上一个按钮
if (self.previousClickedTabBarButton == button) {
//发布通知
[[NSNotificationCenter defaultCenter] postNotificationName:XFJTabBarButtonDidRepeatClickNotification object:nil userInfo:nil];
}
//将当前的按钮赋值作为上一个按钮
self.previousClickedTabBarButton = button;
}
—-> 2.4 监听通知(注意监听通知是在发布通知之前)
#pragma mark - 监听通知
- (void)addNoticeoberver
{
//监听tabBar重复点击通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(repeatedButtonClick) name:XFJTabBarButtonDidRepeatClickNotification object:nil];
}
—-> 2.5 移除通知
#pragma mark - 移除通知
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self forKeyPath:XFJTabBarButtonDidRepeatClickNotification];
}
—-> 2.6 在监听通知的方法中我们实现方法(在方法中仅仅是调用开始头部刷新是不能达到效果的,需要作出判断才能运行的合理)
#pragma mark - 实现tabBar按钮重复点击的监听方法
- (void)repeatedButtonClick
{
//判断当前控制器是否为窗口控制器
if (self.tableView.window == nil) {
return;
}
//判断当前控制器是否为可视控制器
if (self.tableView.scrollsToTop == NO) {
return;
}
[self headerBeginRefresh];
}
3 采用重写代理的方法实现监听
—-> 3.1 说明: 1.1> 不能直接来到创建tabBar对象的方法中直接将tabBar的代理设置为控制器.(设置了,没有准守协议是不包警告的,这和其他对象设置代理有区别)
tabBar.delegate = self;//不要这么设置代理,不会给警告,因为tabBar的内部已经准守了协议
1.2> 代码:下面代码这样设置代理的话,会报错.
#pragma mark - 自定义tabBar
- (void)setupTabBar
{
XMGTabBar *tabBar = [[XMGTabBar alloc] init];
// 替换系统的tabBar KVC:设置readonly属性
[self setValue:tabBar forKey:@"tabBar"];
tabBar.delegate = self;
}
1.3> 错误提示.与原因
/*
Changing the delegate of 【a tab bar managed by a tab bar controller】 is not allowed.
被UITabBarController所管理的UITabBar,它的delegate不允许被修改
*/
—> 说明: 2 > 我们只需要重写代理的方法就可以了
#pragma mark - tabbar的代理方法
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
// 这里不需要调用super,因为父类没有实现这个代理方法
NSLog(@"%s",__func__);
}
四 顶部标题刷新
1 注意点:
—-> 1.1 顶部标题的复按刷新功能和底部的一样做法
—-> 1.2 注意发布通知的标题最好用不一样的
—-> 1.3 监听通知的方法中同样需要作出判断
五 刷新部分会出现的BUG
1 屏幕旋转后会出现点击按钮后不再刷新的功能(BUG)
—-> 1.1 出现BUG原因: 当屏幕旋转的时候,又会调用layoutSubviews重新赋值,所以会监听不到刷新的按钮.(因为旋转后按钮的尺寸明显的会增大,那么自然而然就会调用layoutSubviews).
—-> 1.2 解决思路: 1> 既然旋转屏幕就调用layoutSubviews,那么我们可以用一次性代码(dispatch_once)控制该方法调用的次数; 2> 加一个判断
//判断(角标为0并且上一个按钮状态为空,表示是第0个按钮,并且将值作为上一个按钮的状态)
if (i == 0 && self.previousClickedTabBarButton == nil) {
self.previousClickedTabBarButton = button;
}
—-> 1.3 开发中需要考虑到的后果: 有些方法会重复的执行,就需要考虑到重复执行的后果.
2 如果我们仅仅是直接用通知,不在监听通知的方法中做出判断的话,那么当主屏幕离开视线的时候,点击tabBar中的其他按钮,列表又会刷新,那么我们就需要做出判断.并且两句判断是不能缺少的.(这也是开发中会遇见的BUG 2.6中两个判断方法).
3 我们一侧滑页面,就会立刻刷新(BUG)
—-> 3.1 产生原因: 当我们侧滑的时候,机会来到下面的方法.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
//计算拖动索引
NSInteger index = scrollView.contentOffset.x / XFJ_screenW;
//根据索引取出对应的按钮
XFJTitleButton *titleButton = self.titleView.subviews[index];
//方法
[self titleButtonClick:titleButton];
}
—-> 说明: 方法内部有一个调用点击按钮的方法,就意味着点击了按钮,所以就会刷新.
—-> 3.2 解决办法: 我们在一拖动UIScrollView就会调用的方法中加入下面代码.
//判断如果上一次的按钮和这次的按钮相同就直接返回
if (self.previousButton == titleButton) return;
六 自定义cell
1 通过分析采取方案: 1> 一个父类负责cell的顶部和底部控件,五个子类继承父类负责各自的cell的情况(通过xib) 2> 一个父类负责cell的顶部和底部控件,五个子类继承父类负责各自的cell的情况(用纯代码做,父类的上部控件和底部控件也要写) 3> 只创建一个类,其他类需要的就动态添加.
2 结论: 方法(1)和方法(2)不推荐使用,代码量太大,并且方法(1)是不可能实现的,因为xib是不存在继承的关系,所以我们采用第(3)种方法.
3 模型(通过查看完整的app和plist文件我们需要在模型中定义以下属性)
/**
* 踩(hate)
*/
@property (nonatomic, assign) NSInteger cai;
/**
* 顶(love)
*/
@property (nonatomic, assign) NSInteger love;
/**
* 帖子描述
*/
@property (nonatomic, strong) NSString *text;
/**
* 发帖时间
*/
@property (nonatomic, strong) NSString *passtime;
/**
* 用户名
*/
@property (nonatomic, strong) NSString *name;
/**
* 转发
*/
@property (nonatomic, assign) NSInteger repost;
/**
* 评论
*/
@property (nonatomic, assign) NSInteger comment;
/**
* 头像
*/
@property (nonatomic, strong) NSString *profile_image;
/**
* 在模型中定义一个记录cell高度的属性
*/
@property (nonatomic, assign) CGFloat cellHeight;
/**
* 最热评论(从请求的数据转为plist文件中可以看出,最热评论是数组)
*/
@property (nonatomic, strong) NSArray *top_cmt;
/**
* 帖子类型
*/
@property (nonatomic, assign) NSInteger type;
4 自定义cell(文件和xib)
—-> 4.1 文件
—-> 4.2 xib(一律都采用自动布局)
5 在设置cell内容的方法中调用模型的set方法,直接来到自定义cell的文件中重写模型属性的set方法
#pragma mark - cell的内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
XFJTopicesViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
cell.topices = self.item[indexPath.row];
return cell;
}
6 通过xib连线设置自定义cell文件中的属性
/**
* 头像
*/
@property (weak, nonatomic) IBOutlet UIImageView *profileImageView;
/**
* 用户名
*/
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
/**
* 正文
*/
@property (weak, nonatomic) IBOutlet UILabel *text_Label;
/**
* 发帖时间
*/
@property (weak, nonatomic) IBOutlet UILabel *passtimeLabel;
/**
* 顶帖
*/
@property (weak, nonatomic) IBOutlet UIButton *loveButton;
/**
* 踩帖
*/
@property (weak, nonatomic) IBOutlet UIButton *caiButton;
/**
* 转发数
*/
@property (weak, nonatomic) IBOutlet UIButton *repostButton;
/**
* 评论
*/
@property (weak, nonatomic) IBOutlet UIButton *commentButton;
/**
* 最热评论的父控件UIView
*/
@property (weak, nonatomic) IBOutlet UIView *topCmtView;
/**
* 最热评论文字
*/
@property (weak, nonatomic) IBOutlet UILabel *topCmtLabel;
7 在自定义cell 的文件中用模型给xib中的属性赋值
#pragma mark - 用于给模型赋值
- (void)setTopices:(XFJItem *)topices
{
_topices = topices;
//头像
[self.profileImageView sd_setImageWithURL:[NSURL URLWithString:topices.profile_image] placeholderImage:[UIImage imageNamed:@"defaultUserIcon"]];
//用户名
self.nameLabel.text = topices.name;
//正文
self.text_Label.text = topices.text;
//发帖时间
self.passtimeLabel.text = topices.passtime;
//对帖子的顶
[self setUpButton:self.loveButton number:topices.love placeholder:@"顶"];
//对帖子的踩
[self setUpButton:self.caiButton number:topices.cai placeholder:@"踩"];
//对帖子的转发数
[self setUpButton:self.repostButton number:topices.repost placeholder:@"分享"];
//对帖子的评论
[self setUpButton:self.commentButton number:topices.comment placeholder:@"评论"];
}
8 判断是否有人们评论帖子(有些帖子并没有热门评论,这样的话我们需要控制好尺寸,该方法也是在给xib中的属性赋值的方法中进行的)
//判断
if (topices.top_cmt.count) {//如果有最热评论
//不需要将view隐藏
self.topCmtView.hidden = NO;
//并且取出用户名和内容
NSString *userName = topices.top_cmt.firstObject[@"user"][@"username"];
NSString *content = topices.top_cmt.firstObject[@"content"];
//如果没有文字是一个语音评论
if (content.length == 0) {
content = @"[语音评论]";
}
//将值付给最热评论的label
self.topCmtLabel.text = [NSString stringWithFormat:@"%@ : %@",userName,content];
}else{
//如果都不满足就隐藏
self.topCmtView.hidden = YES;
}
9 对底部四个控件中的文字的设置(设计的方法)
- (void)setUpButton:(UIButton *)button number:(NSInteger)number placeholder:(NSString *)placeholder
{
//判断
if (number > 10000.0) {
[button setTitle:[NSString stringWithFormat:@"%.1f万",number / 10000.0] forState:UIControlStateNormal];
}else if (number > 0){
[button setTitle:[NSString stringWithFormat:@"%zd",number] forState:UIControlStateNormal];
}else{
[button setTitle:placeholder forState:UIControlStateNormal];
}
}
七 计算cell的高度
1 我们通过在模型中取出对应的行直接点出cell的高度(下面代码)
#pragma mark - cell的高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return self.item[indexPath.row].cellHeight;
}
2 我们在模型中自定义一个属性,用来记录cell的高度(第3点说明了),很明显上面的代码中是直接调用cellHeight的get方法,那么我们就重写模型中cellHeight属性的get方法,将计算好的cell直接返回给地阿莱方法,直接根据每个模型中的数据返回cell的高度.(说明:下面采用的做法是xib中每一个控件高度的累加)
#pragma makr - 重写cellHeight的get方法
- (CGFloat)cellHeight
{
//判断如果cell的高度设置了就直接返回
if (_cellHeight) return _cellHeight;
//正文的y值
_cellHeight += 55;
//正文的宽度
CGSize cellSize = CGSizeMake(XFJ_screenW - 2 * XFJ_margin, MAXFLOAT);
_cellHeight += [self.text boundingRectWithSize:cellSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName :[UIFont systemFontOfSize:15]} context:nil].size.height + XFJ_margin;
//判断如果不是段子那么就是声音,图片和视频
if (self.type != XFJTopicTypeWord) {
_cellHeight += 100;
}
//如果有最热评论
if (self.top_cmt.count) {
_cellHeight += 21;
//取出用户名
NSString *userName = self.top_cmt.firstObject[@"user"][@"username"];
//取出评论内容
NSString *content = self.top_cmt.firstObject[@"content"];
//判断没有文字评论,是语音评论
if (content.length == 0) {
content = @"[语音评论]";
}
//拼接评论的文字
NSString *topCmtText = [NSString stringWithFormat:@"%@ : %@",userName,content];
//计算文字评论的高度
_cellHeight += [topCmtText boundingRectWithSize:cellSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:16]} context:nil].size.height + XFJ_margin;
}
//加上工具条
_cellHeight += 35 + XFJ_margin;
return _cellHeight;
}
八 cell的估算高度
1 如果我们不直接采用在模型中设置cellHeight计算cell的高度,而是采用估算高度来设置会对heightForRowAtIndexPath这个方法有怎么样的影响呢?
2 估算高度代码设置
self.tableView.estimatedRowHeight = 170;
3 估算高度的优点与缺点
—-> 3.1 优点一: 会减少heightForRowAtIndexPath方法的调用次数
—-> 3.2 优点二: 可以暂时看不见cell的高度的延迟计算
—-> 3.3 缺点一: contentSize是不太准确的
—-> 3.4 缺点而: 滑动过程中,滚动条的长度会变来变去(可能会有跳跃效果)
4 解析heightForRowAtIndexPath方法的调用时刻
—-> 4.1 如果没有设置估算高度estimatedRowHeight
——–> 4.1.1 每当reloadData时,有多少条数据,就会调用多少次这个方法(比如一共有100条数据,就会调用100次这个方法)
——–> 4.1.2 每当有cell出现时,就会调用一次这个方法
—-> 4.2 如果设置了估算高度estimatedRowHeight
——–> 4.2.1 每当有cell出现时,就会调用一次这个方法
九 知识点和BUG补充
1 知识点: 除了前面所说的刷新数据方式,苹果还自带了刷新类(只要实现了下面代码,什么事情都不需要做)
UIRefreshControl *control = [[UIRefreshControl alloc] init];
[control addTarget:self action:@selector(loadNewTopics) forControlEvents:UIControlEventValueChanged];
[control beginRefreshing];
[self.tableView addSubview:control];
2 注意: 苹果自带的刷新控件会有很多的问题,在开发中一般不会使用苹果自带的,因为会出现一系列的问题.
3 BUG: 很多人刷新数据的时候,特别喜欢在请求数据的方法中加入下面代码,然后就会出现重重的下拉刷新,程序会崩溃,轻微的下拉刷新是没有问题的.
//清空模型
[self.item removeAllObjects];
—-> 3.1 下拉刷新的目的: 旧的数据抛弃,用请求到的新的数据将旧的数据覆盖住,所以很多的初学者会这样处理数据.但是要考虑到,如果请求失败怎么办,而又将原来的数据清空了,那么不就什么都没有了么,所以这句是不应该加上去的.
—-> 3.2 错误提示:
//错误提示:-[__NSArrayM objectAtIndex:]: index 10 beyond bounds for empty array‘
//*** First throw call stac
—-> 3.3 错误解析: 当用户往下一拉,整个tableView处于下拉刷新状态,意味着有一部分数据离开屏幕进入缓存池,但是之后如果写了清空模型的方法,那么当用户一松手的时候,由于tableView需要回到顶部,那么就会调用cellForRow的数据源方法,从缓存池中取,但是又因为清空过了数组,所以找不到缓存池中的cell,所以就会报数组越界.但是当用户轻拖的时候就不会有问题,因为轻拖的时候,cell并没有进入缓存池,所以就不存在数组越界.这是很多初学者会犯的错误.
十 总结
1 该模块是在补充了上一篇刷新部分缺少的地方,也可以说是完善吧,里面说明了现有的app中所有的刷新方式,所以现在大家只要能搞明白这些,那么刷新数据发这部分就不会有问题了.
2 里面还讲到了一些在开发中常出现的BUG,我也附上了BUG解决的办法,希望能给你们一些帮助,最后如果大家觉得我写的还可以的话,那么请关注我的官方博客,我会定期的为大家写上我最近在做的一些项目,有什么问题可以及时给我留言,谢谢!!!!
时间: 2024-10-18 07:01:33