從需求說起
前幾天接到一個(gè)版本,里面包含了一個(gè)和滴滴預(yù)約用車選擇時(shí)間的picker一樣,需要選擇當(dāng)前時(shí)間的后面幾天內(nèi)的時(shí)間,包含了日期,小時(shí)和分鐘數(shù),分鐘數(shù)的間隔是以10分鐘為單位,如下圖所示:
當(dāng)接到這個(gè)需求時(shí),我的心里是有點(diǎn)小拒絕的,看著就是一個(gè)pickerView但是里面東西還是有的東西的,包含:
- 時(shí)間數(shù)據(jù)源獲取,獲取當(dāng)前時(shí)間到3天后。
- 自定義時(shí)間數(shù)據(jù)源,分鐘時(shí)間刻度單位為10分鐘,不足10分鐘的向上取整。
- 選擇當(dāng)天對(duì)當(dāng)前小時(shí)數(shù)據(jù)和分鐘數(shù)據(jù)的處理。
- 選擇當(dāng)前小時(shí)情況下對(duì)分鐘數(shù)據(jù)源的處理。
- pickerView自定義展示(顏色,字體大?。?/li>
個(gè)人認(rèn)為,能自己做的盡量都少用三方庫(kù),減少對(duì)三方庫(kù)的依賴,(PS:目前項(xiàng)目用了百度地圖,iOS12刪除了百度SDK用到的系統(tǒng)庫(kù),各種麻煩),所以決定自己造一個(gè)輪子。
過程
獲取天數(shù)
這里采用NSDate的dateWithTimeIntervalSinceNow函數(shù)再轉(zhuǎn)成字符串,值得一提的是NSDateFormatter,根據(jù)官方文檔的描述:
Creating a date formatter is not a cheap operation. If you are likely to use a formatter frequently, it is typically more efficient to cache a single instance than to create and dispose of multiple instances. One approach is to use a static variable.
創(chuàng)建一個(gè)formatter實(shí)例的代價(jià)是比較高,頻繁使用時(shí)要考慮緩存,個(gè)人的做法是:
+ (void)load {
if (!_dateFormatter) {
_dateFormatter = [[NSDateFormatter alloc] init];
[_dateFormatter setDateFormat:@"YYYY-MM-dd HH:mm"];
}
}
保證只創(chuàng)建一個(gè)NSDateFormatter實(shí)例,關(guān)于+load不做多說,想了解更多的可以看看之前的Runtime源碼 +load 和 +initialize
for (NSInteger i = 0; i < kDays; i++) {
NSString *dateString = [self distanceDate:beginTime aDay:i];//獲取第i天的日期
NSString *week = [self currentWeek:dateString type:NO];//獲取星期幾
}
這里用到了
static NSInteger const kDays = 3;//從今天起能選擇多少天 默認(rèn)3天
因?yàn)?define在編譯的預(yù)處理階段有一個(gè)宏替換操作,大量地使用#define會(huì)拖慢編譯速度,而且宏沒有類型,不做任何類型檢查。Apple官方也是使用了更多的const。
分鐘數(shù)向上取整
需求是當(dāng)分鐘數(shù)不為整10分鐘時(shí),向上取整,比如,16->20,41->50,所以對(duì)初始的數(shù)據(jù)源還有一步向上取整的操作:
NSString *beginTime = [self getTimerAfterCurrentTime:kBenginTimeDely];//開始時(shí)間(也就是當(dāng)前時(shí)間20分鐘后)
NSInteger currentMin = [self getMString:beginTime];
if (currentMin % kTimeInterval != 0) {
beginTime = [self getTimerAfterTime:beginTime periodMin:(kTimeInterval - currentMin % kTimeInterval)];//開始時(shí)間向上取整
}
這里把這三個(gè)數(shù)據(jù)抽取出來,提高靈活性,比如天數(shù)要5天之后or時(shí)間間隔要改成5分鐘or最早時(shí)間是30分鐘后這里只需要修改對(duì)應(yīng)常量即可。
選中數(shù)據(jù)的處理
第一次的做法是在- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component中切換日期或者小時(shí)的時(shí)候重新計(jì)算數(shù)據(jù)源,但是發(fā)現(xiàn)這樣的效果并不好,有明顯的卡頓現(xiàn)象,想起來這樣的真的是很愚蠢的辦法。應(yīng)該初始化的時(shí)候計(jì)算好數(shù)據(jù)源,而不是每次都重新計(jì)算。
在切換日期或者小時(shí)數(shù)的時(shí)候切換數(shù)據(jù)源,具體實(shí)現(xiàn):
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if (component == 0) {
return self.dataSourceModel.dateArray.count;
} else if (component == 1) {
if (self.selectedDateIndex == 0) {//選中的今天
return self.dataSourceModel.todayHourArray.count;
} else {
return self.dataSourceModel.hourArray.count;
}
} else {
if (self.selectedHourIndex == 0 && self.selectedDateIndex == 0) {//選中的當(dāng)天的第一個(gè)小時(shí)
return self.dataSourceModel.todayMinuteArray.count;
} else {
return self.dataSourceModel.minuteArray.count;
}
}
}
關(guān)于數(shù)據(jù)源的計(jì)算,比較直觀,這里就不貼出來了,詳情請(qǐng)看QFDatePickerView中QFTimerUtil文件+ (QFTimerDataSourceModel *)configDataSource方法
pickerView自定義展示
可以直接通過默認(rèn)的代理方法- (nullable NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component __TVOS_PROHIBITED實(shí)現(xiàn)日期顯示,但是這樣的展示效果卻和設(shè)計(jì)圖差距較大,所以實(shí)現(xiàn)了- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UILabel *)recycledLabel自定義展示:
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UILabel *)recycledLabel {
if (!recycledLabel) {
recycledLabel = [[UILabel alloc] init];
}
recycledLabel.textAlignment = NSTextAlignmentCenter;
[recycledLabel setFont:[UIFont systemFontOfSize:18]];
recycledLabel.textColor = [UIColor colorWithRed:34.0f / 255.0f green:34.0f / 255.0f blue:34.0f / 255.0f alpha:1.0f];
...
recycledLabel.text = minModel.showMinuteString;
return recycledLabel;
}
使用方式
手動(dòng)拖入文件夾 或者 pod 'QFDatePicker'
導(dǎo)入QFTimerPicker頭文件,在對(duì)應(yīng)的地方調(diào)用picker的初始化方法和show方法:
/**
初始化時(shí)間選擇
@param block 回調(diào)block 參數(shù)即是選擇的日期
@return 時(shí)間選擇器實(shí)例
*/
- (instancetype)initWithResponse:(ReturnBlock)block;
/**
初始化時(shí)間選擇
@param superView 時(shí)間選擇器的父View,若為空,將時(shí)間選擇器加載在window上面
@param block 回調(diào)block 參數(shù)即是選擇的日期
@return 時(shí)間選擇器實(shí)例
*/
- (instancetype)initWithSuperView:(UIView *)superView response:(ReturnBlock)block;
注釋比較清楚了,通過superView參數(shù),控制這個(gè)picker加載在什么視圖上,當(dāng)其為空的時(shí)候加載在window上。
選中的時(shí)間再block中回調(diào)(PS:這里如果把picker設(shè)置為屬性時(shí),考慮循環(huán)強(qiáng)引用的問題)
具體調(diào)用案例:
QFTimerPicker *picker = [[QFTimerPicker alloc]initWithSuperView:self.view response:^(NSString *selectedStr) {
NSLog(@"%@",selectedStr);
[sender setTitle:selectedStr forState:UIControlStateNormal];
}];
[picker show];
總結(jié)
- 數(shù)據(jù)源的預(yù)加載處理
- 對(duì)define和const取舍
- NSDateFormatter的緩存
- 日期類的計(jì)算,比如獲取當(dāng)前時(shí)間,計(jì)算星期幾等