Start with requirements

A few days ago, I received a version, which contains a picker like didi booking car selection time, which needs to select the time in the next few days of the current time, including the date, hours and minutes. The interval of minutes is in 10 minutes, as shown in the figure below:

When I received this request, I felt a little uncomfortable. It looked like a pickerView, but there were some things in it, including:

  1. Time data source to obtain the current time 3 days later.
  2. User-defined time data source. The unit of the minute scale is 10 minutes. Rounded up if the time scale is less than 10 minutes.
  3. Select how to process the current hour data and minute data on the current day.
  4. Select what to do with the minute data source in the current hour case.
  5. PickerView custom display (color, font size)

Personally think, can do their own as far as possible with less tripartite library, reduce the dependence on tripartite library, (PS: the current project uses Baidu map, iOS12 deleted baidu SDK used system library, all kinds of trouble), so I decided to build a wheel.

process

Get number of days

Using NSDate dateWithTimeIntervalSinceNow function again into string here, it is worth mentioning NSDateFormatter, according to the description of the official document:

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.

Creating a Formatter instance can be expensive, and caching should be considered for frequent use. Personal practice:

+ (void)load {
    if(! _dateFormatter) { _dateFormatter = [[NSDateFormatter alloc] init]; [_dateFormattersetDateFormat:@"YYYY-MM-dd HH:mm"]; }}Copy the code

Ensure that only one instance of NSDateFormatter is created. We won’t say much about + Load. For more information, see the previous Runtime source code + Load and + Initialize

for(NSInteger i = 0; i < kDays; i++) { NSString *dateString = [self distanceDate:beginTime aDay:i]; // Get the date of day I NSString *week = [self currentWeek:dateStringtype:NO]; // Get the day of the week}Copy the code

This is used here

static NSInteger const kDays = 3; // How many days can be selected from today. Default: 3 daysCopy the code

Because #define has a macro replacement operation during the preprocessing phase of compilation, heavy use of #define will slow down compilation, and macros have no type and do no type checking. Apple officially uses more const.

The minutes are rounded up

For example,16->20,41->50. Therefore, there is another round up operation for the initial data source:

NSString *beginTime = [self getTimerAfterCurrentTime:kBenginTimeDely]; // Start time (that is, 20 minutes after the current time) NSInteger currentMin = [self getMString:beginTime];
    if (currentMin% kTimeInterval ! = 0) { beginTime = [self getTimerAfterTime:beginTime periodMin:(kTimeInterval - currentMin% kTimeInterval)]; // Start time rounded up}Copy the code

Here, these three data are extracted to improve flexibility. For example, the number of days after 5 days or time interval should be changed to 5 minutes or the earliest time is 30 minutes after 5 days. Here, you only need to modify the corresponding constant.

Select data processing

The first one is in – (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row InComponent :(NSInteger) recalculate data source when switching dates or hours inComponent, but find that this does not work well and is obviously stalling. The data source should be computed at initialization, not recalculated every time.

Switch data source when switching date or hour.

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
    if (component == 0) {
        return self.dataSourceModel.dateArray.count;
    } else if (component == 1) {
        if(self.selectedDateIndex == 0) {// Select todayreturn self.dataSourceModel.todayHourArray.count;
        } else {
            returnself.dataSourceModel.hourArray.count; }}else {
        if(self.selectedhourIndex == 0 && self.selectedDateIndex == 0) {// The first hour of the selected dayreturn self.dataSourceModel.todayMinuteArray.count;
        } else {
            returnself.dataSourceModel.minuteArray.count; }}}Copy the code

QFDatePickerView QFTimerUtil + (QFTimerDataSourceModel *)configDataSource method

PickerView custom display

You can use the default proxy method directly – (Nullable NSString *)pickerView (UIPickerView *)pickerView titleForRow (NSInteger)row ForComponent :(NSInteger)component __TVOS_PROHIBITED So implement – (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component ReusingView :(UILabel *)recycledLabel custom display:

- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UILabel *)recycledLabel {
    if(! recycledLabel) { recycledLabel = [[UILabel alloc] init]; } recycledLabel.textAlignment = NSTextAlignmentCenter; [recycledLabelsetFont:[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;
}
Copy the code

use

Manually drag in the folder or pod ‘QFDatePicker’ import the QFTimerPicker header and call picker’s initialization and show methods where appropriate:

/** initialization time select @param block The callback block argument is the selected date @returnTime selector instance */ - (instancetype)initWithResponse:(ReturnBlock)block; /** Select @param superView time selector parent View, if empty, load time selector above window @param block callback block argument is the selected date @returnTime picker instance */ - (instancetype)initWithSuperView:(UIView *)superView response:(ReturnBlock)block;Copy the code

The superView argument controls which view the picker is loaded on, and when it is empty it is loaded on the window.

The selected time is called back in the block.

Specific call cases:

QFTimerPicker *picker = [[QFTimerPicker alloc]initWithSuperView:self.view response:^(NSString *selectedStr) {
        NSLog(@"% @",selectedStr);
        [sender setTitle:selectedStr forState:UIControlStateNormal];
    }];
[picker show];
Copy the code

conclusion

  1. Preloading processing of data sources
  2. Choose between define and const
  3. NSDateFormatter cache
  4. Date class calculations, such as getting the current time, calculating the day of the week, etc

Demo cocoaPods installation