六天完成一个简单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,一只长大很久但还没有二够的家伙。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏jeremy的技术点滴

atom-shell小例子

49670
来自专栏雪胖纸的玩蛇日常

Vue+Django2.0 REST framework打造前后端分离的生鲜电商项目(三)设计数据库以及导入原始数据

55950
来自专栏QQ音乐技术团队的专栏

Android系统线控和歌曲信息屏显的那点事

目前Android系统中主流的音乐播放器都支持线控的功能,线控设备包括有线耳机和蓝牙耳机或蓝牙车机,当不方便操作手机的时候可以通过线控来控制音乐的播...

60890
来自专栏Android群英传

Badge分析&如何逼死处女座

17930
来自专栏iOSDevLog

用Kotlin破解Android版微信小游戏-跳一跳成果跳一跳思路源码使用方法参考来源Android 插件 免PC

41560
来自专栏木子昭的博客

<技术贴>当图虫遇到爬虫...根据”分类名称”,获取json数据根据json数据,获取图集url与title爬虫架构:运行界面:最终效果

首先,图虫网是一个很棒的图片网站,这里的爬虫只是为了研究技术,请读者朋友们,不要大量采集网站信息,爬取的图片,请取得版权后再使用... ? 图虫网 根据”分类...

41490
来自专栏SAP最佳业务实践

SAP S/4HANA最佳业务实践:Order-to-Cash订单到收款-3合同处理

•The tile Manage Sales Contracts is part of the business catalog Sales –Contract...

40590
来自专栏Android 开发学习

音频开发ijkplayer小结 android

30820
来自专栏生信技能树

爬虫写完了,运行了,然后呢?

看到这个问题,我首先想到的是R和python。基础的爬虫无非是:构建URL、根据页面结构解析爬取关键信息、整理数据格式输出结果。

28930
来自专栏Objective-C

iOS-MVVM 模式简单演练

30850

扫码关注云+社区

领取腾讯云代金券