Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >六天完成一个简单iOS App - 第四天

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

作者头像
xx_Cc
发布于 2018-05-10 03:38:31
发布于 2018-05-10 03:38:31
1.5K00
代码可运行
举报
运行总次数:0
代码可运行

第四天任务:

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

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

精华页面的搭建

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

精华页面效果

精华模块的页面布局结构

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

  1. 创建子控制器,并为精华控制器CLEssenceViewController添加子控制器
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-(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
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-(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。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 为主控制器添加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方法,来取消按钮的高亮状态

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-(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切换界面

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 标题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的代理方法对页面滑动的监听

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 添加子控制器
-(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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[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控件赋值,造成代码臃肿。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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中修改就可以了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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显示内容即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 开始滑动
- (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]。

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

请求新数据

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-(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];
    }];
}

加载更多数据

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-(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的使用非常简单,右上角更多按钮点击事件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
- (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时,就显示顶,或者踩等汉字。抽取一个方法来处理。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-(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上

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 日期的处理
-(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方法只在类加载时调用一次。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
+(instancetype)calendar
{
    if ([NSCalendar respondsToSelector:@selector(calendarWithIdentifier:)]) {
        return [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
    }else{
        return [NSCalendar currentCalendar];
    }
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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来设置语言区域

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 设置语言区域(因为这种时间是欧美常用时间)
fmt.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];

热门评论的显示和处理

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

热门评论数据

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,一只长大很久但还没有二够的家伙。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016.10.08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
六天完成一个简单iOS App - 第三天
第三天任务: 今天主要任务完成我的模块的搭建。 我的页面的搭建 清除缓存功能 方法抽取总结 我的页面的搭建 我们先来看一下我的界面内容 我的界面分析 通过上面图片可以看出,我的界面是一个非常简单的ta
xx_Cc
2018/05/10
9350
MJRefresh 源码解析
MJRefresh是李明杰老师的作品,到现在已经有1w+颗star了,是一个简单实用,功能强大的iOS下拉刷新(也支持上拉加载更多)控件。它的可定制性很高,几乎可以满足大部分下拉刷新的设计需求,值得学习。
用户2932962
2018/08/30
1.2K0
MJRefresh 源码解析
六天完成一个简单iOS App - 第六天
第六天任务 推荐标签页面的完成 圆形头像的设置和封装 评论界面的完成 新帖界面的完成 发布界面的完成 推荐标签页面的完成 点击精华页面左上角按钮来到推荐标签界面。 推荐标签界面 推荐标签的实
xx_Cc
2018/05/10
1.4K0
iOS开发之多表视图滑动切换示例(仿"头条"客户端)
  好长时间没为大家带来iOS开发干货的东西了,今天给大家分享一个头条新闻客户端各个类别进行切换的一个示例。在Demo中对所需的组件进行的简单封装,在封装的组件中使用的是纯代码的形式,如果想要在项目中进行使用,稍微进行修改即可。   废话少说,先介绍一下功能点,下图是整个Demo的功能点,最上面左边的TabBarButtonItem是用来减少条目的,比如下图有三个按钮,点击减号会减少一个条目。右边的为增加一个条目。点击相应的按钮是切换到对应的表视图上,下方红色的是滑动的指示器,同时支持手势滑动。运行具体效果
lizelu
2018/01/11
3.8K0
iOS开发之多表视图滑动切换示例(仿"头条"客户端)
六天完成一个简单iOS App - 第五天
第五天任务 今天主要完成精华页面中cell内内容的处理。 cell高度的计算 cell中间内容的显示 精华模块的重构 查看图片 保存图片到相册 cell高度的计算 cell间距的设置,每个cell之间有10的间距,因为cell的重用机制,我们发现即使在tableView :didDeselectRowAtIndexPath方法中通过点击cell,减少cell的高度,当cell重新显示的时候还是会变回原来的高度,并且系统内部对cell进行了一些处理,已经在内部设置好cell的frame,所以我们通过重写cel
xx_Cc
2018/05/10
9320
六天完成一个简单iOS App - 第一天
项目介绍 仿照百思不得姐,通过看李明杰老师视频学习自己实践并简单总结项目开发过程中普遍遇到的问题,并且将可以用到其他项目中的分类方法进行简单总结,便于以后在别的项目中使用。 每天任务 1. 实现相应功能 2. 代码重构,简单优化 第一天任务: 配置项目基本环境 搭建框架 代码重构 配置项目基本环境 一. 接口获取 我们可以通过Charles等工具抓包来获取我们想做的App的接口,然后通过解析将每个接口的数据解析出来。也可以去知乎中有趣的 API 接口推荐找找看。 二. 项目图片获取方式 图片的获
xx_Cc
2018/05/10
1.2K0
iOS开发之新浪微博山寨版代码优化
  之前发表过一篇博客“IOS开发之新浪围脖”,在编写代码的时候太偏重功能的实现了,写完基本功能后看着代码有些别扭,特别是用到的四种cell的类,重复代码有点多,所以今天花点时间把代码重构一下。为了减少代码的重复编写把cell中相同的部分抽象成父类,然后继承。不过也是结合着storyboard做的。在优化时转发的View和评论的View相似,于是就做了个重用。在原来的代码上就把cell的代码进行了重写,所以本篇作为补充,关键代码还得看之前的博客。   1.第一种cell,只有微博内容,没有图片,效果如下:
lizelu
2018/01/11
7770
iOS开发之新浪微博山寨版代码优化
iOS开发之多表视图滑动切换示例(仿"头条"客户端)---优化篇
  前几天发布了一篇iOS开发之多表视图滑动切换示例(仿"头条"客户端)的博客,之所以写这篇博客,是因为一位iOS初学者提了一个问题,简单的写了个demo做了个示范,让其在基础上做扩展和改进。被CocoaChina中iOS模块所收录实在出乎我的意料,链接地址(http://www.cocoachina.com/ios/20150706/12370.html),在CocoaChina上看了下面的评论,Demo的问题确实有,优化和改进的空间也是蛮大的。首先内存问题是必须考虑的,不能把这么多的TabalView实
lizelu
2018/01/11
2.1K0
iOS开发之多表视图滑动切换示例(仿"头条"客户端)---优化篇
模拟京东商城实现导航条隐藏功能
样式需求展示-京东导航条 :.gif 需求说明: 1.导航条隐藏功能 2.界面向上滚动的时候,导航条隐藏 3.界面向下滚动的时候,导航条显示 层次结构分析: 核心思路:导航条必须隐藏,显示的顶部
小蠢驴打代码
2018/05/24
1.9K0
iOS开发一款小巧简洁的日历控件 原
        日 历是iOS开发中有时会用到的一个UI控件,网上开源的代码也很多,我浏览过一些,大致有两种模式,一种是日历的逻辑由开发者自己实现,通过计算闰年与平 年来确定月份天数,另外一种模式是通过NSDate这个时间类,来获取日历的信息。我个人认为后一种更加安全,代码性能也会更加优质,下面就是我用这种模 式实现的一个日历控件。
珲少
2018/08/16
3.7K0
iOS开发一款小巧简洁的日历控件
                                                                            原
RxSwift + MJRefresh 打造自动处理刷新控件状态
本文是基于 iOS - RxSwift 项目实战记录 所述,如果你还未阅读过,建议你最好还先阅读一遍,并下载Demo熟悉一下 : ) LXFBiliBili 前言 MVVM的模式中,多出了View
LinXunFeng
2018/06/29
2K0
实现 iOS 无感知上拉加载更多
什么是无感知,这个这样理解:在网络情况正常的情况下,用户对列表进行连续的上拉时,该列表可以无卡顿不停出现新的数据。
网罗开发
2021/08/13
2.5K0
【IOS开发基础系列】下拉刷新专题
        如果你装了xcode_4.5_developer_preview,那么在UITableViewController.h文件中你会看到,UITableViewController里面有如下声明,说明UITableViewController已经内置了UIRefreshControl控件
江中散人_Jun
2023/10/16
3030
【IOS开发基础系列】下拉刷新专题
MJRefresh 源码阅读
MJRefresh项目地址 https://github.com/CoderMJLee/MJRefresh 下载下来后我们打开项目可以看到下面的目录 MJ项目结构 MJRefresh目录下就是下
用户2215591
2018/06/06
1.2K0
iOS面试题梳理(三)
在某个方法中 self.name = _name,name = _name 它 们有区别吗,为什么? 1.前者是存在内存管理的setter方法赋值,它会对_name对象进行保留或者拷贝操作,后者是普
Jacklin
2018/05/15
1.4K0
关于刘海打理这种事儿,美团点评的iOS工程师早就有经验了,不信你看!
背景 iPhone X 刘海机于9月13日发布,给科技小春晚带来一波高潮。作为开发人员却多出来一份忧虑,iPhone X 怎么适配?我们 App 的脑袋会不会也长一刘海出来?Tabbar 会不会被圆角
美团技术团队
2018/03/13
2.2K0
关于刘海打理这种事儿,美团点评的iOS工程师早就有经验了,不信你看!
你可能需要为你的APP适配iOS11
作 者 sonia,腾讯移动客户端开发 工程师 商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处。 WeTest 导读  iOS 11 为整个生态系统的 UI 元素带来了一种更加大胆、动态的新风格。 本文介绍了iOS11在UI方面做了哪些更新,有些更新可以为用户提供更加完美的体验,但也有的可能会给目前的APP带来异常bug。 前言 前几天发现在做的APP在iOS11系统上动画有异常,在其他系统的设备上都是正常的,动画的操作是观察tableView的contentOffset变化后执行的,异常
WeTest质量开放平台团队
2023/05/04
9220
你可能需要为你的APP适配iOS11
六天完成一个简单iOS App - 第二天
第二天任务: 项目主框架搭建完毕后,就可以从各个模块入手完成项目,这里从最简单的关注模块开始。 关注页面的搭建 登录界面的搭建 方法抽取与知识点总结 一. 关注页面的搭建 关注页面我们这里只做未登录的
xx_Cc
2018/05/10
2.2K0
iOS学习——UIPickerView的实现年月选择器
  最近项目上需要用到一个选择器,选择器中的内容只有年和月,而在iOS系统自带的日期选择器UIDatePicker中却只有四个选项如下,分别是时间(时分秒)、日期(年月日)、日期+时间(年月日时分)以及倒计时。其中并没有我们所需要的只显示年月的选择器,在网上找了很多相关的资料,但是觉得都写得过于麻烦。因此,为了满足项目需求,自己用UIPickerView写了一个只显示年月的选择器界面,同时还可以控制我们的显示的最小时间。当然,如果要控制其他内容也都是可以的,无非就是在数据处理上多一些处理和控制。 typed
mukekeheart
2018/03/26
4.8K0
iOS学习——UIPickerView的实现年月选择器
iOS-QQ音乐播放器的简单实现
一. QQ音乐播放器的简单实现 每个音乐播放器的实现都大致相同,个人认为难点在于歌曲播放与Slider的同步,歌词的解析与播放的同步。这些过程虽然繁琐,但是理解起来并不难。先来看看简单实现结果吧。 Q
xx_Cc
2018/05/10
2.9K0
推荐阅读
相关推荐
六天完成一个简单iOS App - 第三天
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验