從需求說起
前幾天接到一個版本,裡面包含了一個和滴滴預約用車選擇時間的picker一樣,需要選擇當前時間的後面幾天內的時間,包含了日期,小時和分鐘數,分鐘數的間隔是以10分鐘為單位,如下圖所示:
當接到這個需求時,我的心裡是有點小難受的,看著就是一個pickerView但是裡面東西還是有的東西的,包含:
- 時間資料來源獲取,獲取當前時間到3天后。
- 自定義時間資料來源,分鐘時間刻度單位為10分鐘,不足10分鐘的向上取整。
- 選擇當天對當前小時資料和分鐘資料的處理。
- 選擇當前小時情況下對分鐘資料來源的處理。
- pickerView自定義展示(顏色,字型大小)
個人認為,能自己做的儘量都少用三方庫,減少對三方庫的依賴,(PS:目前專案用了百度地圖,iOS12刪除了百度SDK用到的系統庫,各種麻煩),所以決定自己造一個輪子。
過程
獲取天數
這裡採用NSDate的dateWithTimeIntervalSinceNow
函式再轉成字串,值得一提的是NSDateFormatter
,根據官方文件的描述:
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.
建立一個formatter例項的代價是比較高,頻繁使用時要考慮快取,個人的做法是:
+ (void)load {
if (!_dateFormatter) {
_dateFormatter = [[NSDateFormatter alloc] init];
[_dateFormatter setDateFormat:@"YYYY-MM-dd HH:mm"];
}
}
複製程式碼
保證只建立一個NSDateFormatter例項,關於+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;//從今天起能選擇多少天 預設3天
複製程式碼
因為#define在編譯的預處理階段有一個巨集替換操作,大量地使用#define會拖慢編譯速度,而且巨集沒有型別,不做任何型別檢查。Apple官方也是使用了更多的const。
分鐘數向上取整
需求是當分鐘數不為整10分鐘時,向上取整,比如,16->20,41->50,所以對初始的資料來源還有一步向上取整的操作:
NSString *beginTime = [self getTimerAfterCurrentTime:kBenginTimeDely];//開始時間(也就是當前時間20分鐘後)
NSInteger currentMin = [self getMString:beginTime];
if (currentMin % kTimeInterval != 0) {
beginTime = [self getTimerAfterTime:beginTime periodMin:(kTimeInterval - currentMin % kTimeInterval)];//開始時間向上取整
}
複製程式碼
這裡把這三個資料抽取出來,提高靈活性,比如天數要5天之後or時間間隔要改成5分鐘or最早時間是30分鐘後這裡只需要修改對應常量即可。
選中資料的處理
第一次的做法是在- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
中切換日期或者小時的時候重新計算資料來源,但是發現這樣的效果並不好,有明顯的卡頓現象,想起來這樣的真的是很愚蠢的辦法。應該初始化的時候計算好資料來源,而不是每次都重新計算。
在切換日期或者小時數的時候切換資料來源,具體實現:
- (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) {//選中的當天的第一個小時
return self.dataSourceModel.todayMinuteArray.count;
} else {
return self.dataSourceModel.minuteArray.count;
}
}
}
複製程式碼
關於資料來源的計算,比較直觀,這裡就不貼出來了,詳情請看QFDatePickerView中QFTimerUtil
檔案+ (QFTimerDataSourceModel *)configDataSource
方法
pickerView自定義展示
可以直接通過預設的代理方法- (nullable NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component __TVOS_PROHIBITED
實現日期顯示,但是這樣的展示效果卻和設計圖差距較大,所以實現了- (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;
}
複製程式碼
使用方式
手動拖入資料夾 或者 pod 'QFDatePicker'
匯入QFTimerPicker標頭檔案,在對應的地方呼叫picker的初始化方法和show方法:
/**
初始化時間選擇
@param block 回撥block 引數即是選擇的日期
@return 時間選擇器例項
*/
- (instancetype)initWithResponse:(ReturnBlock)block;
/**
初始化時間選擇
@param superView 時間選擇器的父View,若為空,將時間選擇器載入在window上面
@param block 回撥block 引數即是選擇的日期
@return 時間選擇器例項
*/
- (instancetype)initWithSuperView:(UIView *)superView response:(ReturnBlock)block;
複製程式碼
註釋比較清楚了,通過superView
引數,控制這個picker載入在什麼檢視上,當其為空的時候載入在window上。
選中的時間再block中回撥(PS:這裡如果把picker設定為屬性時,考慮迴圈強引用的問題)
具體呼叫案例:
QFTimerPicker *picker = [[QFTimerPicker alloc]initWithSuperView:self.view response:^(NSString *selectedStr) {
NSLog(@"%@",selectedStr);
[sender setTitle:selectedStr forState:UIControlStateNormal];
}];
[picker show];
複製程式碼
總結
- 資料來源的預載入處理
- 對define和const取捨
- NSDateFormatter的快取
- 日期類的計算,比如獲取當前時間,計算星期幾等