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

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

下面是效果图

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

需求有下面的几点:

  • 上面是频道栏目 可以左右滑动进行切换
  • 下面是促销商品的列表
  • 商品栏目数目为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 不会回调 减少性能消耗

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,563评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,263评论 25 707
  • 2017.02.22 可以练习,每当这个时候,脑袋就犯困,我这脑袋真是神奇呀,一说让你做事情,你就犯困,你可不要太...
    Carden阅读 1,316评论 0 1