iOS仿滴滴預約用車時間選擇器

Ly夢k發表於2018-11-23

從需求說起

前幾天接到一個版本,裡面包含了一個和滴滴預約用車選擇時間的picker一樣,需要選擇當前時間的後面幾天內的時間,包含了日期,小時和分鐘數,分鐘數的間隔是以10分鐘為單位,如下圖所示:

timerPicker

當接到這個需求時,我的心裡是有點小難受的,看著就是一個pickerView但是裡面東西還是有的東西的,包含:

  1. 時間資料來源獲取,獲取當前時間到3天后。
  2. 自定義時間資料來源,分鐘時間刻度單位為10分鐘,不足10分鐘的向上取整。
  3. 選擇當天對當前小時資料和分鐘資料的處理。
  4. 選擇當前小時情況下對分鐘資料來源的處理。
  5. 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;
        }
    }
}
複製程式碼

關於資料來源的計算,比較直觀,這裡就不貼出來了,詳情請看QFDatePickerViewQFTimerUtil檔案+ (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];
複製程式碼

總結

  1. 資料來源的預載入處理
  2. 對define和const取捨
  3. NSDateFormatter的快取
  4. 日期類的計算,比如獲取當前時間,計算星期幾等

演示Demo
cocoaPods安裝

相關文章