倒計時是一個常用的功能頁面, 該功能可以分解成3步: ①倒計時UI的展示 ②定時數(shù)據(jù)的格式與解析 ③定時器的選擇
下面通過3個知識點來完成整個功能
1. UIDatePicker
UIDatePicker 是一個系統(tǒng)封裝好控制器類,封裝了 UIPickerView,但是他是UIControl的子類,專門用于接受日期、時間和持續(xù)時長的輸入,我們對于時間選擇的UI可以直接使用該類去完成,下面貼出具體使用代碼
@property (nonatomic, strong) UIDatePicker *datePicker; //設(shè)置為屬性
- (void)configPickerView{ //創(chuàng)建
UIDatePicker *datePicker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0, 100, self.view.frame.size.width, 200)];
//設(shè)置地區(qū): zh-中國
datePicker.locale = [NSLocale localeWithLocaleIdentifier:@"zh"];
//設(shè)置日期模式(Displays month, day, and year depending on the locale setting)
datePicker.datePickerMode = UIDatePickerModeCountDownTimer;
//設(shè)置當(dāng)前顯示時間
//需要轉(zhuǎn)換的字符串
NSString *dateString = @"5"; //格式2018-11-22 08:08:08對應(yīng)yyyy-MM-dd HH:mm:ss 初始化為5
//設(shè)置轉(zhuǎn)換格式
NSDateFormatter *formatter = [[NSDateFormatter alloc] init] ;
[formatter setDateFormat:@"mm"]; //對應(yīng)設(shè)置的5為分鐘
//NSString轉(zhuǎn)NSDate
NSDate *date=[formatter dateFromString:dateString];
[datePicker setDate:date animated:YES];
//監(jiān)聽DataPicker的滾動
[datePicker addTarget:self action:@selector(dateChange:) forControlEvents:UIControlEventValueChanged];
self.datePicker = datePicker;
[self.view addSubview:self.datePicker];
}
這里需要注意datePickerMode方法,系統(tǒng)提供了多種形態(tài)的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;
對應(yīng)樣式如下
1.1 UIDatePickerModeTime

1.2 UIDatePickerModeDate

1.3 UIDatePickerModeDateAndTime

1.4 UIDatePickerModeCountDownTimer

2. Cron表達式
cron表達式就是對時間數(shù)據(jù)的一種處理,可以和后臺商量好都使用這個方式去解析時間數(shù)據(jù),舉個例子:8 27 22 1 3 ? 2019,代表時間2019年3月1日22點27點8秒,具體代碼如下
獲取當(dāng)前時間值(秒為單位)
NSDate* nowDate = [NSDate date];
NSTimeZone* zone = [NSTimeZone timeZoneWithName:@"Asia/Shanghai"];
NSTimeInterval time = [zone secondsFromGMTForDate:nowDate];// 以秒為單位返回當(dāng)前時間與系統(tǒng)格林尼治時間的差
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傳入下面參數(shù),即可獲得當(dāng)前時間的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;
//執(zhí)行一次 應(yīng)該設(shè)置 日月年, 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;
}
倒計時的本質(zhì)也就是對比時間差,所以小伙伴要根據(jù)實際項目中的數(shù)據(jù)做處理,cron表達式只是其中一種,下面介紹另一種時間數(shù)據(jù)處理
#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;
// 當(dāng)前時間字符串格式
NSString *nowDateStr = [dateFomatter stringFromDate:nowDate];
// 截止時間data格式
NSDate *expireDate = [dateFomatter dateFromString:expireDateStr];
// 當(dāng)前時間data格式
nowDate = [dateFomatter dateFromString:nowDateStr];
// 當(dāng)前日歷
NSCalendar *calendar = [NSCalendar currentCalendar];
// 需要對比的時間數(shù)據(jù)
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有很多缺點,如
①循環(huán)引用導(dǎo)致內(nèi)存泄漏
②因為受runloop影響定時可能不準(zhǔn)確
③代碼繁多
使用dispatch_source_t有效的避免上面問題
①將self作為傳入方法,避免了循環(huán)引用
②底層語言實現(xiàn),不依賴runloop不會出現(xiàn)線程擁堵導(dǎo)致的定時不準(zhǔn)確問題
③block塊代碼看起來更簡潔,方便管理
具體代碼如下
#pragma mark 開啟定時器
- (void)createGCDTimer:(NSString *)schedule{
//獲得服務(wù)器傳輸過來的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];
//得到當(dāng)前時間
NSDate* nowDate = [NSDate date];
NSTimeZone* zone = [NSTimeZone timeZoneWithName:@"Asia/Shanghai"];
NSTimeInterval time = [zone secondsFromGMTForDate:nowDate];// 以秒為單位返回當(dāng)前時間與系統(tǒng)格林尼治時間的差
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;
//定時器的創(chuàng)建
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); //每秒執(zhí)行
dispatch_source_set_event_handler(self.timer, ^{
if(timeout <= 0){ //倒計時結(jié)束,關(guān)閉
dispatch_source_cancel(self.timer);
dispatch_async(dispatch_get_main_queue(), ^{
//設(shè)置界面的按鈕顯示 根據(jù)自己需求設(shè)置
NSLog(@"倒計時結(jié)束");
});
}else{
int hour = timeout / 3600;
int minute = timeout / 60 % 60;
int second = timeout % 60;
dispatch_async(dispatch_get_main_queue(), ^{
//設(shè)置界面的按鈕顯示 根據(jù)自己需求設(shè)置
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); //關(guān)閉定時器
}
- (void)dealloc
{
NSLog(@"釋放了我就放心了!!!"); //可以在dealloc看一下有沒有l(wèi)og的打印
}
完整定時功能Demo下載地址:
https://github.com/gaoyuGood/UIDatePicker-Cron-dispatch_source_t