六天完成一个简单iOS App - 第四天

第四天任务:

今天主要任务完成精华模块的搭建。

  1. 精华页面的搭建
  2. 精华页面中全部界面的显示
  3. 日期的处理
  4. 热门评论的显示和处理

精华页面的搭建

精华页面分为全部、视频、声音、图片、段子五个界面,五个界面可以通过点击导航栏下面的titleView进行页面的切换,也可以通过手指滑动来进行页面的切换,所以经过分析我们已经能大致了解到精华模块的页面布局结构。

精华页面效果
精华模块的页面布局结构

从图中可以看出,精华控制器CLEssenceViewController(以下简称主控制器)的View上先是一个ScrollView用来存放精华控制器的五个子控制器,五个子控制器的View并排存放,每个View的frame为屏幕大小。titleView也是添加在主控制器上,显示在scrollView上面,保证titleView永远显示在主控制器的View上,不会随着scrollView的滚动而滚动。

  1. 创建子控制器,并为精华控制器CLEssenceViewController添加子控制器
-(void)setUpChildViewController
{
       CLAllViewController *all = [[CLAllViewController alloc]init];
       [self addChildViewController:all];
       CLVideoViewController *video = [[CLVideoViewController alloc]init];
       [self addChildViewController:video];
       CLVoiceViewController *voice = [[CLVoiceViewController alloc]init];
       [self addChildViewController:voice];
       CLPictureViewController *picture = [[CLPictureViewController alloc]init];
       [self addChildViewController:picture];
       CLWordViewController *word = [[CLWordViewController alloc]init];
       [self addChildViewController:word];
}
  1. 为主控制器View添加ScrollView
-(void)setUpScrollView
{
       UIScrollView *scrollView = [[UIScrollView alloc]initWithFrame:[UIScreen mainScreen].bounds];
       scrollView.backgroundColor = CLCommonColor(206);
       [self.view addSubview:scrollView];
       scrollView.pagingEnabled = YES;
       scrollView.showsHorizontalScrollIndicator = NO;
       scrollView.showsVerticalScrollIndicator = NO;
       NSInteger count = [self.childViewControllers count];
       scrollView.contentSize = CGSizeMake(self.view.cl_width * count, 0);
       scrollView.delegate = self;
       self.scrollView = scrollView;
}
  1. 为主控制器添加titleView,titleView中button使用自定义CLTitleButton,便于在自定义CLTitleButton内部设置button标题,颜色,字体大小等。另外titleView种还有指示条indicatorView。
// 为主控制器添加titleView
-(void)setUpTitlesView
{
       UIView *titleView = [[UIView alloc]initWithFrame:CGRectMake(0, 64, self.view.cl_width, 35)];
        titleView.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.7];
        self.titlesView = titleView;
        CGFloat buttonW = titleView.cl_width / 5.0;
        CGFloat buttonH = titleView.cl_height;
        NSArray *titlesArr = @[@"全部",@"视频",@"声音",@"图片",@"段子"];
        NSInteger count = [titlesArr count];
        for (int i= 0; i < count ; i ++) {
            CLTitleButton *titleButton = [CLTitleButton buttonWithType:UIButtonTypeCustom];
            titleButton.tag = i;
            titleButton.frame = CGRectMake(i * buttonW, 0, buttonW, buttonH);
            [titleButton setTitle:titlesArr[i] forState:UIControlStateNormal];
            [titleButton addTarget:self action:@selector(titleClick:) forControlEvents:UIControlEventTouchUpInside];
            [titleView addSubview:titleButton];
        }
        [self.view addSubview:titleView];
        UIView *indicatorView = [[UIView alloc]init];
        // 也可以取出button selecter状态下的颜色
        // UIButton *button = titleView.subviews.lastObject;
        // indicatorView.backgroundColor = [button titleColorForState:UIControlStateSelected];
        indicatorView.backgroundColor = [UIColor redColor];
        indicatorView.cl_height = 2;
        indicatorView.cl_y = titleView.cl_height - 2;
        [titleView addSubview:indicatorView];
        self.indicatorView = indicatorView;
        // 页面一显示就选中第一个button 且不需要动画
        CLTitleButton *button = titleView.subviews.firstObject;
        [button.titleLabel sizeToFit];
        button.selected = YES;
        self.selectedButton = button;
        indicatorView.cl_width = button.titleLabel.cl_width + 6;
        indicatorView.cl_centerX = button.cl_centerX;
}

自定义CLTitleButton内部设置,通过重写覆盖系统的setHighlighted方法,来取消按钮的高亮状态

-(instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame: frame]) {
        [self setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal];
        [self setTitleColor:[UIColor redColor] forState:UIControlStateSelected];
        self.titleLabel.font = [UIFont systemFontOfSize:14];
    }
    return self;
}
-(void)setHighlighted:(BOOL)highlighted
{
    
}
@end

4. 接下来需要做一些业务逻辑的处理,例如(1)当页面一显示的时候就默认显示全部页面,也就相当于点击了全部按钮。(2)当点击别的按钮时,页面切换到别的页面,并将按钮置于选中状态,将之前被点击的按钮置于未选中状态,并将button下面指示条移动到现在button下面。(3)当手指滑动界面进行切换界面时,也将相应的按钮置于选中状态,底部指示条移动到选中按钮,之前的按钮取消选中状态。页面的滑动切换需要用到ScrollView的代理方法对页面的滑动进行判断。
点击button切换界面

// 标题button点击事件
-(void)titleClick:(CLTitleButton *)button
{
    self.selectedButton.selected = NO;
    button.selected = YES;
    self.selectedButton = button;
 
    [UIView animateWithDuration:0.25 animations:^{
        self.indicatorView.cl_width = button.titleLabel.cl_width + 6;
        self.indicatorView.cl_centerX = button.cl_centerX;
    }];
    
    CGPoint offset = self.scrollView.contentOffset;
    offset.x = button.tag * self.view.cl_width;
    [self.scrollView setContentOffset:offset animated:YES];
}

ScrollView的代理方法对页面滑动的监听

#pragma mark UIScrollViewDelegate代理方法
// 滑动结束时,一定要调用[setcontentoffset animated ] 或者 [scrollerRactVisible animaated]方法让scroll产生滚动动画,动画结束时才会调用
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
     [self addChildVcView];
}
// 减速完成 也就是滑动完成
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    // 选中 点击对应的按钮
    int index = scrollView.contentOffset.x / scrollView.cl_width;
    // 添加子控制器
    [self addChildVcView];
    CLTitleButton *button = self.titlesView.subviews[index];
    [self titleClick:button];
}

注意:代理方法 didEndScrollingAnimation 一定要调用[setcontentoffset animated ]或者 [scrollerRactVisible animaated]方法让scroll产生滚动动画,动画结束时才会调用。也就是说即使调用了[setcontentoffset animated ]方法,但是如果scrollView的contentoffset并没有改变也不会调用 didEndScrollingAnimation方法。
didEndDecelerating人手动滑动,滑动停止时才会调用。
5. 简单优化,页面View的懒加载实现
页面加载完成显示的时候我们只能看到全部页面的内容,但是此时却在加载完成时将五个控制器的View全部加载完成,并且显示了cell的内容,但是其中有四个页面我们并没有去看,这显然占用了大量的内存,这是没有必要的。因此考虑使用控制器View的懒加载,当View要显示的时候我们才去加载他,并将View显示在屏幕上。而其他没有显示的控制器View就不去加载他。如图所示

View的懒加载

从图中可以看出,点击了图片界面,只加载了图片界面,但是其他三个 视频、音频、段子控制器的View并没有加载。也就是当点击了button或者滑动界面之后,在根据scrollView的偏移量判断需要加载哪个控制器的View,然后将View添加到scrollView中。

// 添加子控制器
-(void)addChildVcView
{
    int index = self.scrollView.contentOffset.x / self.scrollView.cl_width;
    UIViewController *childVc = self.childViewControllers[index];
//    childVc.view.frame = CGRectMake(index * self.scrollView.cl_width, 0, self.scrollView.cl_width, self.scrollView.cl_height);
    //可以化简成一句代码
    childVc.view.frame = self.scrollView.bounds;
    [self.scrollView addSubview:childVc.view];    
}

注意:这里可能会有一个疑惑,我们在button点击事件中和scrllView的滑动代理方法中都有将子控制器View添加到scrollView即[self.scrollView addSubview:childVc.view];,那岂不是每次点击button或者滑动都会重新添加一个子控制器View到scrollView上?其实这里add方法是不会重复添加的,即使添加成千上万次也只会添加一次。

至此,精华界面的搭建已经基本完成,接下来要做的就是内容的显示,以及内容中一些细节之处的设置。下面先来完成全部界面的内容显示,因为全部界面包含视频,音频,图片,段子四个界面全部内容,将全部界面显示完全,其他界面就非常简单了。

精华页面中全部界面的显示

自定义cell的分析,因为全部页面中有4种cell,4种cell顶部和底部都是一样的唯有中间部位不一样。这里自定义cell有两种方案。

  1. 使用继承,父类cell显示顶部和底部等一些相同的控件,中间内容由四种类型不同的cell继承父类自己显示,这样做功能独立清晰,每种cell显示自己中间内容即可,但是这种方法没有办法使用xib来描述cell,需要使用纯代码。
  2. 全部使用一种cell,先将顶部底部描述出来,中间不一样的地方放什么,视情况而定,中间部分在代码中动态添加。

因为cell内内容比较多,而且需要添加约束,这里采用第二种方法,下图为cell的xib布局


cell的xib布局

其中添加自动布局约束是比较麻烦的,但是只要细心一步一步添加,就可以约束成功,添加约束还是多多练习熟练之后还是有很多便捷之处。
全部控制器加载cell

[self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([CLTopicCell class]) bundle:nil] forCellReuseIdentifier:CLTopicCellID];

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CLTopicCell *cell = [tableView dequeueReusableCellWithIdentifier:CLTopicCellID];
    cell.topic = self.topicArr[indexPath.row];
    return cell;
}

请求数据
请求数据使用AFN,同样给cell添加模型属性Topic,通过setTopic方法给cell上控件赋值,避免在tableView: cellForRowAtIndexPath方法中给cell控件赋值,造成代码臃肿。

下拉刷新上拉加载
系统提供了下拉刷新的方法

UIRefreshControl *control = [[UIRefreshControl alloc] init];
[control addTarget:self action:@selector(loadNewTopics:) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:control];

// 结束刷新
[control endRefreshing];

系统提供的刷新方法有很多局限性,这里使用MJRefresh实现下拉刷新和上拉加载,创建自己的刷新控件继承自MJRefresh,通过重写-(void)prepare方法对刷新控件进行一些个性化设置。这里拿CLRefreshHeader举例,CLRefreshFooter相同
CLRefreshHeader.m

#import "CLRefreshHeader.h"
@implementation CLRefreshHeader
-(void)prepare
{
    [super prepare];
    self.automaticallyChangeAlpha = YES;
    self.lastUpdatedTimeLabel.textColor = [UIColor orangeColor];
    self.stateLabel.textColor = [UIColor orangeColor];
    [self setTitle:@"赶紧下拉吧" forState:MJRefreshStateIdle];
    [self setTitle:@"赶紧松开吧" forState:MJRefreshStatePulling];
    [self setTitle:@"正在加载数据..." forState:MJRefreshStateRefreshing];    
}

这样使用起来就非常方便了,并且易于管理,如果想要修改刷新控件的样式,只需要在CLRefreshHeader中修改就可以了。

self.tableView.mj_header = [CLRefreshHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewTopics)];
// 一显示全部界面就刷新一次
[self.tableView.mj_header beginRefreshing];
self.tableView.mj_footer = [CLRefreshFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreTopics)];  

// 请求数据完成之后关闭刷新
[self.tableView.mj_header endRefreshing];

MJRefresh内部实现思路,在tableView中titleView上方添加下拉刷新的View,使用scrollView代理方法监听tableView的contentOffset,当开始下拉,contentOffset改变时显示刷新View,当滑动结束并且contentOffset到达一定数值时,修改刷新View显示内容即可。

// 开始滑动
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (scrollView.contentInset.top == 149) return;
    if (scrollView.contentOffset.y <= - 149.0) {
        self.label.text = @"松开立即刷新";
    } else {
        self.label.text = @"下拉可以刷新";
    }
}
//  滑动结束
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (scrollView.contentOffset.y <= - 149.0) { // 进入下拉刷新状态
        self.label.text = @"正在刷新";
        [UIView animateWithDuration:0.5 animations:^{
            UIEdgeInsets inset = scrollView.contentInset;
            inset.top = 149;
            scrollView.contentInset = inset;
        }];
        // 停两秒滑动回去
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [UIView animateWithDuration:0.5 animations:^{
                UIEdgeInsets inset = scrollView.contentInset;
                inset.top = 99;
                scrollView.contentInset = inset;
            }];
        });
    }
}

上拉加载和下拉刷新思路一样,有两种方案,1. 当滑动到最低端时,提示用户上拉加载更多。2. 当滑动到最低端时,自动加载下一页内容。

同时上拉和下拉出现的问题
当我们下拉刷新的时候,在数据还没有返回刷新成功的时候,又滑动到底部上拉加载了新数据,此时就会造成数据混乱,如果上拉加载更多的数据已经返回,此时下拉刷新的数据也返回了,就只剩下最新的数据了。因此当上拉和下拉同时出现的时候必须要取消掉先开始的上拉或者下拉请求。

  1. 保存task,上拉和下拉同时出现时,取消其中一个。
  2. 使用AFN manager manager.tasks 里面装着所有请求,遍历取消。
  3. 使用[manager.task makeobjectsPerformSelect:@selsct(canle)];数组方法,让数组里面所有对象都执行这个方法
  4. [manager invalidateSessionCanceingTask:YES]吧session给杀死并且取消任务,这样意味着manager以后没有办法发送请求了 (谨慎使用)。

常见分页情况

  1. 发送page参数 : page = 2 加载第二页的数据,每一页几条,当获取下一页时,如果有新的数据添加到最前面,就会发生数据重复显示。
    例:服务器数据库的数据 = @[23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10]每次加载5条。
    第1页数据 == @[20, 19, 18, 17, 16]
    发送page参数 : page=2,此时有新数据加入
    第2页数据 == @[18, 17, 16, 15, 14]
    就会出现数据重复显示

  2. 发送maxid参数: maxid = 16 加载小于16的数据每次几条,比较严谨,保证数据衔接性,不会重叠。maxid请求的第2页数据为 == @[15, 14, 13, 12, 11]。

当然两种分页方法影响并不大,要根据服务器返回的数据,确定分页请求方法。

请求新数据

-(void)loadNewTopics
{
    // 数组里面的task全部取消
    [self.manager.tasks makeObjectsPerformSelector:@selector(cancel)];
    NSMutableDictionary *parameter = [NSMutableDictionary dictionary];
    parameter[@"a"] = @"list";
    parameter[@"c"] = @"data";
    parameter[@"type"] = @"1";
    [self.manager GET:CLCommonURL parameters:parameter progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        self.maxtime = responseObject[@"info"][@"maxtime"];
        self.topicArr = [CLTopic mj_objectArrayWithKeyValuesArray:responseObject[@"list"]];      
        [self.tableView reloadData];
        CLLog(@"请求成功");
        // 让[刷新控件]结束刷新
        [self.tableView.mj_header endRefreshing];
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        CLLog(@"请求失败 -- %@",error);
        // 让[刷新控件]结束刷新
        [self.tableView.mj_header endRefreshing];
    }];
}

加载更多数据

-(void)loadMoreTopics
{
    // 数组里面的task全部取消
    [self.manager.tasks makeObjectsPerformSelector:@selector(cancel)];
    NSMutableDictionary *parameter = [NSMutableDictionary dictionary];
    parameter[@"a"] = @"list";
    parameter[@"c"] = @"data";
    parameter[@"maxtime"] = self.maxtime;
    parameter[@"type"] = @"1";
    [self.manager GET:CLCommonURL parameters:parameter progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {  
        self.maxtime = responseObject[@"info"][@"maxtime"];
        NSArray<CLTopic *> *moreTopics = [CLTopic mj_objectArrayWithKeyValuesArray:responseObject[@"list"]];
        [self.topicArr addObjectsFromArray:moreTopics];
        [self.tableView reloadData];
        CLLog(@"请求成功");
        // 让[刷新控件]结束刷新
        [self.tableView.mj_footer endRefreshing];
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        CLLog(@"请求失败 -- %@",error);
        // 让[刷新控件]结束刷新
        [self.tableView.mj_footer endRefreshing];
    }];
}

此时cell的顶部和底部相同的部分内容已经可以显示。接下来要处理cell内部一些细节问题。
UIAlertController的简单使用
iOS8 之后UIAlertController的使用非常简单,右上角更多按钮点击事件

- (IBAction)moreClick {
    UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"弹出消息标题" message:@"弹出消息内容" preferredStyle:UIAlertControllerStyleActionSheet];
    [controller addAction:[UIAlertAction actionWithTitle:@"收藏" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        CLLog(@"点击了【收藏】");
    }]];
    [controller addAction:[UIAlertAction actionWithTitle:@"举报" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
        CLLog(@"点击了【举报】");
    }]];
    [controller addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        CLLog(@"点击了【取消】");
    }]];
    [self.window.rootViewController presentViewController:controller animated:YES completion:nil];
}

顶、踩等数量的显示的处理
例:当数量超过1万时,会显示1.1万,当小于1万时就显示具体数字,当为0时,就显示顶,或者踩等汉字。抽取一个方法来处理。

-(void)setUpButton:(UIButton *)button Number:(NSInteger)number Placeholder:(NSString *)placeholder
{
    NSString *strNum = [NSString string];
    if (number >= 10000) {
        strNum = [NSString stringWithFormat:@"%.1f万",number / 10000.0];
    }else if (number == 0){
        strNum = placeholder;
    }else{
        strNum = [NSString stringWithFormat:@"%zd",number];
    }
    [button setTitle:strNum forState:UIControlStateNormal];
}

日期时间的处理
系统返回的时间是yyyy-MM-dd HH-mm-ss格式的,我们需要对它进行一些处理
判断是否 今年
判断是否 今天
判断时间间隔 >= 1小时 - @"5小时前"
1小时 > 时间间隔 >= 1分钟 - @"10分钟前"
1分钟 > 分钟 - @"刚刚"
昨天 - @"昨天 09:10:05"
其他 - @"11-20 09:10:05"
非今年 - @"2015-11-20 09:10:05"

在模型中重写时间created_at的get方法,先将时间处理好,然后在显示在cell上

// 日期的处理
-(NSString *)created_at
{
    fmt_.dateFormat = @"yyyy-MM-dd HH-mm-ss";
    NSDate *createdAtDate =  [fmt_ dateFromString:_created_at];
    if (createdAtDate.isThisYear) {// 是今年
        // 判断是否是今天和昨天的方法是iOS8 才有的,如果需要适配iOS7 我们可以自己在分类中实现判断是否为今天和昨天
        if (createdAtDate.isToday) {// 是今天
            // 手机当前时间
            NSDate *nowDate = [NSDate date];
            NSCalendarUnit unit = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
            NSDateComponents *cmps = [calendar_ components:unit fromDate:createdAtDate toDate:nowDate options:0];
            if (cmps.hour >= 1) {// 时间间隔大于一个小时
                return [NSString stringWithFormat:@"%zd小时前",cmps.hour];
            }else if (cmps.minute >= 1){
                return [NSString stringWithFormat:@"%zd分钟前",cmps.minute];
            }else{
                return @"刚刚";
            }
        }else if (createdAtDate.isYesterday){ //是昨天
            fmt_.dateFormat = @"昨天 HH:mm:ss";
            return [fmt_ stringFromDate:createdAtDate];  
        }else{
            fmt_.dateFormat = @"MM-dd HH:mm:ss";
            return [fmt_ stringFromDate:createdAtDate];
        }
    }else{ // 不是今年,直接返回直接即可
        return _created_at;
    }
    return nil;
}

created_at的get方法调用非常频繁,而NSDateFormatter和NSCalendar对象没有必要这么频繁的创建,可以使用懒加载,也可以再initialize方法中创建,initialize方法只在类加载时调用一次。

static NSCalendar *calendar_ ;
static NSDateFormatter *fmt_;
//第一次使用CLTopic类时调用一次
+(void)initialize
{
    calendar_ = [NSCalendar calendar];
    fmt_ = [[NSDateFormatter alloc]init];
}

NScalendar的单例方法[NSCalendar currentCalendar]在iOS8之后有时会发生错误,iOS8之后使用[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];方法,为了适配iOS8之前版本,我们为NScalendar添加分类,添加calendar类方法根据不同版本创建calendar

+(instancetype)calendar
{
    if ([NSCalendar respondsToSelector:@selector(calendarWithIdentifier:)]) {
        return [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
    }else{
        return [NSCalendar currentCalendar];
    }
}

同样,系统在iOS8之后提供了判断是否是今天,昨天的方法
[calendar isDateInToday:createdAtDate];
[calendar isDateInYesterday:createdAtDate];
为了适配iOS8之前版本,我们通过给Data添加分类,自己实现判断是否是今天和昨天

#import "NSDate+CLExtension.h"

@implementation NSDate (CLExtension)

-(BOOL)isThisYear
{
    NSCalendar *calendar = [NSCalendar calendar];
    NSInteger creatYear = [calendar component:NSCalendarUnitYear fromDate:self];
    NSInteger nowYear = [calendar component:NSCalendarUnitYear fromDate:[NSDate date]];
    return creatYear == nowYear;
}
-(BOOL)isToday
{
    NSDateFormatter *fmt = [[NSDateFormatter alloc]init];
    fmt.dateFormat = @"yyyyMMdd";
    NSString *creatStr = [fmt stringFromDate:self];
    NSString *nowStr = [fmt stringFromDate:[NSDate date]];
    return [creatStr isEqualToString:nowStr]; 
}
-(BOOL)isYesterday
{
    NSDateFormatter *fmt = [[NSDateFormatter alloc]init];
    fmt.dateFormat = @"yyyyMMdd";
    NSString *creatStr = [fmt stringFromDate:self];
    NSString *nowStr = [fmt stringFromDate:[NSDate date]];
    NSDate *creatDate = [fmt dateFromString:creatStr];
    NSDate *nowDate = [fmt dateFromString:nowStr];
    NSCalendar *calendar = [NSCalendar calendar];
    NSCalendarUnit unit = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
    NSDateComponents *cmp = [calendar components:unit fromDate:creatDate toDate:nowDate options:0];
    return cmp.year == 0 && cmp.month == 0 && cmp.day == 1;
   // cmp.day = -1即是判断是否是明天
   //return cmp.year == 0 && cmp.month == 0 && cmp.day == -1;
}
@end

日期的处理其实非常简单,只要熟悉NSDateFormatter,NSCalendar类两者结合使用即可完成一般时间的处理。
NSDateFormatter 用来确定时间的格式,string 和date之间的相互转化。
NSCalendar 用来做时间之间的比较。两个时间点的间隔为所有差值相加。
NSCalendarUnit 确定比较的内容,年,月,日等
NSDateComponents 获得比较的结果。

有时服务器返回的时间数据可能是时间戳,时间戳表示从1970年1月1号 00:00:00开始走过的毫秒数。可以通过dateWithTimeIntervalSince1970将时间戳转化为日期时间。

如果返回的是别的区域的时间,也可以通过NSDateFormatter的locale来设置语言区域

// 设置语言区域(因为这种时间是欧美常用时间)
fmt.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];

热门评论的显示和处理

热门评论不是每一条cell都有,通过判断热门评论数组的count,判断有没有热门评论,确定是否显示热门评论View。

热门评论数据

我们需要拿到content 和user里面的username,根据面向模型开发,创建CLComment模型和CLUser模型,直接将数组内热门评论通过MJExtension字典转化为CLConmment模型。

if (topic.top_cmt) {
    self.topCmtView.hidden = NO;
    NSString *userName = topic.top_cmt.user.username;
    NSString *contentText = topic.top_cmt.content;
    if (self.top_cmt.voiceuri.length) {
         contentText = @"[语音评论]";
    }
    self.topCmtContentLabel.text = [NSString stringWithFormat:@"%@ : %@",userName,contentText];
}else{
    self.topCmtView.hidden = YES;  
}

这里有一个注意点,当最热评论是语音的时候,topic.top_cmt.content;值是为空的,这里需要提醒用户最热评论是一条语音。

总结

今天主要完成了精华页面的布局,页面切换的一些逻辑处理,数据请求及上拉下拉刷新加载完成,cell内部一些细节处理。日期的处理等
来看一下第四天的成果吧

第四天效果图

前四天代码已经上传至github--源码下载


文中如果有不对的地方欢迎指出。我是xx_cc,一只长大很久但还没有二够的家伙。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • 第六天任务 推荐标签页面的完成 圆形头像的设置和封装 评论界面的完成 新帖界面的完成 发布界面的完成 推荐标签页面...
    xx_cc阅读 4,468评论 34 45
  • 第五天任务 今天主要完成精华页面中cell内内容的处理。 cell高度的计算 cell中间内容的显示 精华模块的重...
    xx_cc阅读 3,048评论 32 28
  • 1.badgeVaule气泡提示 2.git终端命令方法> pwd查看全部 >cd>ls >之后桌面找到文件夹内容...
    i得深刻方得S阅读 4,628评论 1 9
  • 我也觉得, 人生绝不会是没有希望的将来, 即便再坏也不会比现在更坏, 我相信会峰回路转春暖花开, 还有很多事情可以...
    美目扬阅读 314评论 0 0
  • 世上美丽的情诗有很多很多,但是最幸福的一定是这一句——执子之手,与子偕老。《何以笙箫默》想表达的,就是这么一种幸福...
    wen4e阅读 646评论 1 3