iOS開發-倒計時功能

倒計時是一個常用的功能頁面, 該功能可以分解成3步: ①倒計時UI的展示 ②定時數據的格式與解析 ③定時器的選擇

下面通過3個知識點來完成整個功能

1. UIDatePicker

UIDatePicker 是一個系統封裝好控制器類,封裝了 UIPickerView,但是他是UIControl的子類,專門用於接受日期、時間和持續時長的輸入,我們對於時間選擇的UI可以直接使用該類去完成,下面貼出具體使用代碼

@property (nonatomic, strong) UIDatePicker *datePicker;   //設置爲屬性
- (void)configPickerView{    //創建
    UIDatePicker *datePicker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0, 100, self.view.frame.size.width, 200)];
    
    //設置地區: zh-中國
    datePicker.locale = [NSLocale localeWithLocaleIdentifier:@"zh"];
    
    //設置日期模式(Displays month, day, and year depending on the locale setting)
    datePicker.datePickerMode = UIDatePickerModeCountDownTimer;
    
    //設置當前顯示時間
    //需要轉換的字符串
    NSString *dateString = @"5";   //格式2018-11-22 08:08:08對應yyyy-MM-dd HH:mm:ss   初始化爲5
    //設置轉換格式
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init] ;
    [formatter setDateFormat:@"mm"];  //對應設置的5爲分鐘
    //NSString轉NSDate
    NSDate *date=[formatter dateFromString:dateString];
    
    [datePicker setDate:date animated:YES];
    
    //監聽DataPicker的滾動
    [datePicker addTarget:self action:@selector(dateChange:) forControlEvents:UIControlEventValueChanged];
    
    self.datePicker = datePicker;
    
    [self.view addSubview:self.datePicker];
}

這裏需要注意datePickerMode方法,系統提供了多種形態的UI可供選擇

typedef NS_ENUM(NSInteger, UIDatePickerMode) {
    UIDatePickerModeTime,           // Displays hour, minute, and optionally AM/PM designation depending on the locale setting (e.g. 6 | 53 | PM)
    UIDatePickerModeDate,           // Displays month, day, and year depending on the locale setting (e.g. November | 15 | 2007)
    UIDatePickerModeDateAndTime,    // Displays date, hour, minute, and optionally AM/PM designation depending on the locale setting (e.g. Wed Nov 15 | 6 | 53 | PM)
    UIDatePickerModeCountDownTimer, // Displays hour and minute (e.g. 1 | 53)
} __TVOS_PROHIBITED;

對應樣式如下

1.1 UIDatePickerModeTime
1.2 UIDatePickerModeDate
1.3 UIDatePickerModeDateAndTime
1.4 UIDatePickerModeCountDownTimer

2. Cron表達式

cron表達式就是對時間數據的一種處理,可以和後臺商量好都使用這個方式去解析時間數據,舉個例子:8 27 22 1 3 ? 2019,代表時間2019年3月1日22點27點8秒,具體代碼如下
獲取當前時間值(秒爲單位)

NSDate* nowDate = [NSDate date];
NSTimeZone* zone = [NSTimeZone timeZoneWithName:@"Asia/Shanghai"];
NSTimeInterval time = [zone secondsFromGMTForDate:nowDate];// 以秒爲單位返回當前時間與系統格林尼治時間的差
int newTime = ([nowDate timeIntervalSince1970]+time);
int nowhour = newTime / 3600;
int nowminite = newTime / 60 % 60;
int newsecond = newTime % 60;
nowhour %= 24;
newTime = 3600*nowhour+60*nowminite+newsecond;

在將newTime傳入下面參數,即可獲得當前時間的cron表達式

+(NSString*)cronStringWithtime:(long long)time
                          week:(NSInteger)week
                       weekday:(NSInteger)weekday
{
    NSString* cron;
    
    long hour = time / 3600;
    long min = time / 60 % 60;
    long second = time % 60;
    
    //執行一次 應該設置 日月年, tbd.
    NSString* weekDesp=@"";
    if(week == 0){ //once
        weekDesp = @"?";
        NSDate* date = [NSDate date];
        NSCalendar * calendar = [NSCalendar currentCalendar]; // 指定日曆的算法
        NSDateComponents *comps = [calendar components:kCFCalendarUnitSecond|NSCalendarUnitMinute|NSCalendarUnitHour|NSCalendarUnitDay|NSCalendarUnitMonth|NSCalendarUnitYear fromDate:date];
        long long currentTime=comps.hour*60*60+comps.minute*60+comps.second;
        if (currentTime>=time) {
            NSDate *tomorrowDate = [NSDate dateWithTimeIntervalSinceNow:(24*60*60)];
            comps=[calendar components:kCFCalendarUnitSecond|NSCalendarUnitMinute|NSCalendarUnitHour|NSCalendarUnitDay|NSCalendarUnitMonth|NSCalendarUnitYear fromDate:tomorrowDate];
        }
        cron = [NSString stringWithFormat:@"%ld %ld %ld %ld %ld %@ %ld",second,min,hour,comps.day,comps.month,weekDesp,comps.year];
        return cron;
    }else if(week == 127){ //every day
        weekDesp = @"*";
    }else{
        NSMutableString *weekString = [NSMutableString new];
        NSInteger weekIndex = 0;
        while (week) {
            if (week & 0x1) {
                if (0 != weekString.length) {
                    [weekString appendString:@","];
                }
                [weekString appendFormat:@"%ld", weekIndex+1]; //cron from 1-7
            }
            week >>= 1;
            ++weekIndex;
        }
        weekDesp = weekString;
    }
    
    //Seconds Minutes Hours Day-of-Month Month Day-of-Week Year
    cron = [NSString stringWithFormat:@"%ld %ld %ld ? * %@ *",second,min,hour,weekDesp];   //cron = @"0 0 8 ? * 2,3,4,5,6 *";
    
    return cron;
}

倒計時的本質也就是對比時間差,所以小夥伴要根據實際項目中的數據做處理,cron表達式只是其中一種,下面介紹另一種時間數據處理

#pragma mark - 定時器Hander
- (void)timerHander {
    NSString *redEffectiveTime = @"2019-3-1 16:00:00";
    
    NSDate *nowDate = [NSDate date];
    NSDateFormatter *dateFomatter = [[NSDateFormatter alloc] init];
    dateFomatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    // 截止時間字符串格式
    NSString *expireDateStr = redEffectiveTime;
    // 當前時間字符串格式
    NSString *nowDateStr = [dateFomatter stringFromDate:nowDate];
    // 截止時間data格式
    NSDate *expireDate = [dateFomatter dateFromString:expireDateStr];
    // 當前時間data格式
    nowDate = [dateFomatter dateFromString:nowDateStr];
    // 當前日曆
    NSCalendar *calendar = [NSCalendar currentCalendar];
    // 需要對比的時間數據
    NSCalendarUnit unit = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
    // 對比時間差
    NSDateComponents *dateCom = [calendar components:unit fromDate:nowDate toDate:expireDate options:0];
    
    NSString *day = [NSString stringWithFormat:@"%d",(int)dateCom.day];
    NSString *hours = [NSString stringWithFormat:@"%02d",(int)dateCom.hour];
    NSString *minutes = [NSString stringWithFormat:@"%02d",(int)dateCom.minute];
    NSString *seconds = [NSString stringWithFormat:@"%02d",(int)dateCom.second];
    
    NSString *time = @"";
    if ([day intValue]>0) {
        time = [NSString stringWithFormat:@"剩餘時間:%@天%@:%@:%@",day,hours,minutes,seconds];
    }else{
        time = [NSString stringWithFormat:@"剩餘時間:%@:%@:%@",hours,minutes,seconds];
    }
}

3. dispatch_source_t

定時器選擇了GCD的dispatch_source_t,沒有選擇平時最常用的定時器NSTime,因爲Timer有很多缺點,如

①循環引用導致內存泄漏
②因爲受runloop影響定時可能不準確
③代碼繁多

使用dispatch_source_t有效的避免上面問題

①將self作爲傳入方法,避免了循環引用
②底層語言實現,不依賴runloop不會出現線程擁堵導致的定時不準確問題
③block塊代碼看起來更簡潔,方便管理

具體代碼如下

#pragma mark 開啓定時器
- (void)createGCDTimer:(NSString *)schedule{
    //獲得服務器傳輸過來的cron時間
    NSString* desc = [HYCronTimerUtils displaySecondStringWithcronString:schedule]; //得到12:27
    NSArray *tempArr = [desc componentsSeparatedByString:@":"];  //得到12 和 27
    NSString* hour = tempArr[0];
    NSString* min = tempArr[1];
    NSString* second = tempArr[2];
    int mins = [hour intValue]*3600+[min intValue]*60+[second intValue];
    
    //得到當前時間
    NSDate* nowDate = [NSDate date];
    NSTimeZone* zone = [NSTimeZone timeZoneWithName:@"Asia/Shanghai"];
    NSTimeInterval time = [zone secondsFromGMTForDate:nowDate];// 以秒爲單位返回當前時間與系統格林尼治時間的差
    int newTime = ([nowDate timeIntervalSince1970]+time);
    int nowhour = newTime / 3600;
    int nowminite = newTime / 60 % 60;
    int newsecond = newTime % 60;
    nowhour %= 24;
    newTime = 3600*nowhour+60*nowminite+newsecond;
    
    //得到倒計時時間
    __block int timeout = mins-newTime; 

    //定時器的創建
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
    dispatch_source_set_timer(self.timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //每秒執行
    dispatch_source_set_event_handler(self.timer, ^{
        if(timeout <= 0){ //倒計時結束,關閉
            dispatch_source_cancel(self.timer);
            dispatch_async(dispatch_get_main_queue(), ^{
                //設置界面的按鈕顯示 根據自己需求設置
                NSLog(@"倒計時結束");
            });
        }else{
            int hour = timeout / 3600;
            int minute = timeout / 60 % 60;
            int second = timeout % 60;
            
            dispatch_async(dispatch_get_main_queue(), ^{
                //設置界面的按鈕顯示 根據自己需求設置
                self.timerLabel.text = [NSString stringWithFormat:@"%02d:%02d:%02d",hour,minute,second];
            });
            timeout -= 1;
        }
    });
    dispatch_resume(self.timer);
}

定時器肯定需要注意釋放問題,需要在項目中合適的時機釋放該定時器

#pragma mark 生命週期
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    dispatch_source_cancel(_timer);   //關閉定時器
}

- (void)dealloc
{
    NSLog(@"釋放了我就放心了!!!");   //可以在dealloc看一下有沒有log的打印
}

完整定時功能Demo下載地址:
https://github.com/gaoyuGood/UIDatePicker-Cron-dispatch_source_t

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章