百思不得姐之自定义cell(八)

一 运行图和解析顺序

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解决的办法,希望能给你们一些帮助,最后如果大家觉得我写的还可以的话,那么请关注我的官方博客,我会定期的为大家写上我最近在做的一些项目,有什么问题可以及时给我留言,谢谢!!!!
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值