iOS仿滴滴預(yù)約用車時(shí)間選擇器

從需求說起

前幾天接到一個(gè)版本,里面包含了一個(gè)和滴滴預(yù)約用車選擇時(shí)間的picker一樣,需要選擇當(dāng)前時(shí)間的后面幾天內(nèi)的時(shí)間,包含了日期,小時(shí)和分鐘數(shù),分鐘數(shù)的間隔是以10分鐘為單位,如下圖所示:

timerPicker

當(dāng)接到這個(gè)需求時(shí),我的心里是有點(diǎn)小拒絕的,看著就是一個(gè)pickerView但是里面東西還是有的東西的,包含:

  1. 時(shí)間數(shù)據(jù)源獲取,獲取當(dāng)前時(shí)間到3天后。
  2. 自定義時(shí)間數(shù)據(jù)源,分鐘時(shí)間刻度單位為10分鐘,不足10分鐘的向上取整。
  3. 選擇當(dāng)天對(duì)當(dāng)前小時(shí)數(shù)據(jù)和分鐘數(shù)據(jù)的處理。
  4. 選擇當(dāng)前小時(shí)情況下對(duì)分鐘數(shù)據(jù)源的處理。
  5. 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)看QFDatePickerViewQFTimerUtil文件+ (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é)

  1. 數(shù)據(jù)源的預(yù)加載處理
  2. 對(duì)define和const取舍
  3. NSDateFormatter的緩存
  4. 日期類的計(jì)算,比如獲取當(dāng)前時(shí)間,計(jì)算星期幾等

演示Demo
cocoaPods安裝

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容