美文网首页君赏博客
关于首页倒计时处理一些细节

关于首页倒计时处理一些细节

作者: 君赏 | 来源:发表于2017-03-18 10:55 被阅读51次

    关于首页倒计时处理一些细节

    下面是效果图

    这个模块是展示促销商品模块的:

    需求有下面的几点:

    • 上面是频道栏目 可以左右滑动进行切换
    • 下面是促销商品的列表
    • 商品栏目数目为2 为了后期兼容做成可以左右滚动
    • 当两个其中只有一个已经停止就把停止的商品显示 DEAL ENDED
    • 当两个都已经停止就去除对应的栏目

    本来想把定时器做到 Cell 里面或者上面显示时间的控件里面 开始做的时候没发现什么问题。后来切换了频道,发现其他频道已经两个结束了竟然没有删除对应的栏目

    经过思考,导致这个问题出现的原因是

    促销商品展示的 Cell 是重用的,开始的时候其他栏目是没有赋值的。导致是不能收到已经停止的消息的,自然也就没办法从列表里面进行移除

    解决的方案就是对数据源进行各自的监听,当数据源显示时间已经停止的时候,就移除对应的数据源,重新刷新界面。

    新建一个基类用于管理倒计时GBSaleTimeModel

    因为倒计时只分为还没有开始 正在进行 已经结束三种时间段,我们新建一个 ENUM 类型来标识

    /**
      销售的三种状态
    
     - GBSaleTimeTypeNoStart: 还没有开始
     - GBSaleTimeTypeStarting: 开始销售中
     - GBSaleTimeTypeEnded: 已经结束
     */
    typedef NS_ENUM(NSUInteger, GBSaleTimeType) {
        GBSaleTimeTypeNoStart,
        GBSaleTimeTypeStarting,
        GBSaleTimeTypeEnded
    };
    

    新建一个 typedef用户 Block 进行回调

    /**
     当定时器值改变的时候就回调
    
     @param model GBSaleTimeModel
     */
    typedef void(^GBSaleTimeValueChangeCompletionHandle)(GBSaleTimeModel *model);
    

    让外部可以知道当前的数据源处于什么状态,我们新建一个只读的变量用于让外接知道当前数据源处于什么状态。

    /**
     当前销售状态
     */
    @property (nonatomic, assign, readonly) GBSaleTimeType saleTimeType;
    

    我们是只读的属性 我们实现一个 Get 方法

    - (GBSaleTimeType)saleTimeType {
        NSTimeInterval nowTimeInterval = [[NSDate date] timeIntervalSince1970];
        if (nowTimeInterval < self.startTimeInterval) {
            return GBSaleTimeTypeNoStart;
        } else if (nowTimeInterval < self.endTimeInterval) {
            return GBSaleTimeTypeStarting;
        } else {
            return GBSaleTimeTypeEnded;
        }
    }
    

    因为是获取距离现在时间的状态,自然要获取的是现在时间距离1970的时间戳,比较时间戳。

    如果现在时间戳小于开始的时间戳 标识还没有开始销售

    如果现在的时间戳大于等于开始时间戳并且小于结束的时间戳 标识正在销售

    如果现在的时间戳大于等于结束的时间戳 标识已经结束销售

    因为开始的时间和结束的时间是后段给的 我们无法知道所有新建两个属性 只读让子类实现 get 方法

    /**
     开始时间 子类重写 get 方法
     */
    @property (nonatomic, assign, readonly) NSTimeInterval startTimeInterval;
    
    /**
     结束时间 子类重写 get 方法
     */
    @property (nonatomic, assign, readonly) NSTimeInterval endTimeInterval;
    

    新建一个只读的属性 让外部可以知道现在时间距离开始 或者 结束 时间段 方便做倒计时

    /**
     时间段 如果还没有开始就是现在时间和开始时间的间距 如果是已经开始就等于现在时间和结束时间的间距 如果是已经结束就等于0
     */
    @property (nonatomic, assign, readonly) NSTimeInterval nowTimePeriod;
    
    - (NSTimeInterval)nowTimePeriod {
        NSTimeInterval nowTimeInterval = [[NSDate date] timeIntervalSince1970];
        if (self.saleTimeType == GBSaleTimeTypeNoStart) {
            return self.startTimeInterval - nowTimeInterval;
        } else if (self.saleTimeType == GBSaleTimeTypeStarting) {
            return self.endTimeInterval - nowTimeInterval;
        } else {
            return 0;
        }
    }
    

    我们判断目前的状态 如果是还没有开始就 获取距离开始的时间

    如果是正在销售 就获取距离结束的时间

    如果是已经销售结束 就赋值等于0

    我们新增一个注册监听的方法 让外界监听销售状态和改变倒计时状态

    /**
     注册监听时间改变的回调
    
     @param completionHandle 回调
     */
    - (void)registerSaleTimeValueChangedCompletionHandle:(GBSaleTimeValueChangeCompletionHandle)completionHandle;
    
    - (void)registerSaleTimeValueChangedCompletionHandle:(GBSaleTimeValueChangeCompletionHandle)completionHandle {
        if (!_completionHandleList) {
            _completionHandleList = [NSMutableArray array];
        }
        if (completionHandle) {
            [_completionHandleList addObject:completionHandle];
        }
        [self valueChnaged];
        if (!_saleTimer && self.endTimeInterval > self.startTimeInterval && self.startTimeInterval > 0) {
            _saleTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(valueChnaged) userInfo:nil repeats:YES];
        }
    }
    

    为什么要把 Block 添加到数组里面 ?

    因为要做一个功能 就是让多个人进行监听同一个对象的回调 这个也直接导致下面的一个问题的出现

    为什么要判断 Block 存在再添加呢?

    因为如果外部调用方法不实现 block 就会直接的崩溃

    为什么要在定时器之前还调用一下valueChnaged值改变的方法呢?

    因为可能用户注册的时候 倒计时已经停止 或者 不满足定时器开启的条件 外接就无法得到对应的状态 会出现一些问题无法修复

    - (void)valueChnaged {
        for (int i = 0; i < _completionHandleList.count; i ++) {
            GBSaleTimeValueChangeCompletionHandle completionHandle = _completionHandleList[i];
            completionHandle(self);
        }
        if (self.saleTimeType == GBSaleTimeTypeEnded) {
            [_saleTimer invalidate];
            [_completionHandleList removeAllObjects];
        }
    }
    

    因为要监听还没有显示的模块信息 所以我们在给整个模块赋值的时候 进行遍历销售的商品进行监听倒计时

    如果其中的一组已经全部停止 就删除对应的频道

    - (void)checkSaleItem:(BOOL)needRegister {
        NSMutableArray *nowSaleModels = [NSMutableArray array];
        @weakify(self);
        [_headerModel.homeSaleModels enumerateObjectsUsingBlock:^(GBHomeSaleModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            @strongify(self);
            NSMutableArray *hotSaleGoodList = [NSMutableArray array];
            [obj.hotSaleGoodList enumerateObjectsUsingBlock:^(GBHomeSaleItemModel * _Nonnull obj1, NSUInteger idx1, BOOL * _Nonnull stop) {
                @strongify(self);
                if (obj1.saleTimeType != GBSaleTimeTypeEnded) {
                    // 如果还没有结束才显示
                    [hotSaleGoodList addObject:obj1];
                    if (needRegister) {
                        [obj1 registerSaleTimeValueChangedCompletionHandle:^(GBSaleTimeModel *model) {
                            @strongify(self);
                            if (model.saleTimeType == GBSaleTimeTypeEnded) {
                                [self checkSaleItem:NO];
                                [self registerTableViewGroup];
                                [self.tableView reloadData];
                            }
                        }];
                    }
                }
            }];
            if (hotSaleGoodList.count > 0) {
                [nowSaleModels addObject:obj];
            }
        }];
        _headerModel.homeSaleModels = nowSaleModels;
    }
    
    

    为什么要在方法添加一个是否需要注册的参数呢?

    因为 我们在倒计时结束的时候 重新走了一次本方法 进行数据的筛选。 如果我们每次都注册 导致如果结束的时候回调就会死循环 如果在添加之前判断时候结束 也是可以的

    为什么要做销售商品数组大于零 就添加对应的频道?

    因为之前做的是 如果商品已经销售停止 就删除对应的元素 让界面只显示正在销售的

    后来产品说只显示一个元素界面会不好看 就改成了如果两个都销售停止才删除对应的频道 如果只有一个就让已经销售停止的展示 DEAL ENDED

    我们在销售商品的试图 Cell 上面赋值数据源的地方进行监听倒计时

        [itemModel registerSaleTimeValueChangedCompletionHandle:^(GBSaleTimeModel *model) {
            @strongify(self);
            if (self.itemModel != model) {
                return ;
            }
            self.saleTimeLabel.timeInterval = model.nowTimePeriod;
            NSString *startLabelText;
            if (model.saleTimeType == GBSaleTimeTypeNoStart) {
                startLabelText = @"Starts in";
    
            } else {
                startLabelText = @"Ended in";
            }
            BOOL isHideDealEnded = model.saleTimeType == GBSaleTimeTypeEnded;
            UIColor *startTimeColor = isHideDealEnded ? GB_COLOR_RGB(153, 153, 153, 1.0) : GB_COLOR_RGB(51, 51, 51, 1.0);
            self.startTimeLabel.textColor = startTimeColor;
            self.dealEndedView.hidden = !isHideDealEnded;
            self.startTimeLabel.text = startLabelText;
        }];
    

    这个方法存在 一个严重的问题就是 当切换频道 就会cell 重用 新的数据源就会重新的注册 block

    但是之前注册的 block 还是存在在内存里面就会出现显示的倒计时的时间不正确

            if (self.itemModel != model) {
                return ;
            }
    

    这一句代码就是为了判断如果回调的 block 如果不是现在显示的数据源回调就不会往下继续走

    其实还有一个更好的方法 就是注册的时候保留 block 对象 cell 进行刷新的时候 移除之前注册的

    这样不仅解决了数据源显示错误问题 而且可以让之前注册 block 不会回调 减少性能消耗

    相关文章

      网友评论

        本文标题:关于首页倒计时处理一些细节

        本文链接:https://www.haomeiwen.com/subject/myjunttx.html