作应用的时候免不了会对某些UI控件作一些样式上的定制,好比Button的背景色,圆角,阴影等元素的调整。UIDatePicker也是一个比较经常使用的UI控件,iOS 7简约的设计风格在某些场景下可能并非很合适,因此UIDatePicker
有时也是一个有较大定制需求的控件。可是使人匪夷所思的一点是,尽管UIDatePicker
和UIPickerView
看起来好像是差很少的两个UI组件,可是从iOS的API上来看,这两个组件的类之间并没有继承关系。从类的继承关系上来看:html
UIPickerView
继承自UIView
: UIResponder
: NSObject
UIDatePicker
继承自UIControl
: UIView
: UIResponder
: NSObject
而这两个类之间并没有继承关系。UIControl
这个类是用来处理UI控制事件的,这意味着UIDatePicker
可能对控制事件的处理更加精细,但日常的使用上咱们可能感觉不到与UIPickerView
太多差异。并且要命的一点在于,UIDatePicker
并不支持样式定制,这一点官方文档已经给出来了,连改个单元格的高度和栏目宽度都没戏。因此很遗憾,若是你要改变这玩意的样式,只能另辟蹊径,最方便的搞法就是找长得差很少的UIPickerView
下手。若是你没有太复杂的需求的话,直接定制UIPickerView
多是一个最方便的选择。在个人案例里,暂时只有修改字号、字体,以及行高行宽的需求,因此用这个方法是在合适不过了。ios
修改UIPickerView
的样式须要了解这个类相关的两个Protocol:UIPickerViewDataSource
和UIPickerViewDelegate
。
若是对UITableView
比较熟悉的话,看到这两个Protocol的名字应该能很快猜出接下来是个什么搞法。事实上,UITableView
,UICollectionView
和UIPickerView
这三个是差很少的设计。最复杂的是UICollectionView
,搞明白了这个,另外两个看一眼就知道怎么回事。这里咱们很少分析这三个组件的共同点。直接讲如何定制。app
在此咱们须要建立一个新的类,并实现这两个Protocol中的一些方法:字体
UIPickerViewDataSource
中有两个-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
//返回栏目数量,时间日期通常能够用三栏(年、月、日)-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
//每一栏的行数-(CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component
//用来修改行高-(CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component
//修改每一栏的宽度-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
//选中某栏目某行后的动做-(NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component
//返回一个带属性的字符串(包含字体信息),若是只是修改字号,能够实现这个方法-(UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view
//返回一个UIView类实例,若是须要对每一个cell的可重用视图进行定制,能够实现这个方法。-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
//若是没有字体定制需求,实现这个方法最简单。这里最关键的方法是返回每一栏行数的方法以及最后返回跟title有关的方法。实现的思想是,首先要肯定给出的时间范围,全部展现的选项不能超过这个范围。其次就是每一行应该对应显示哪一条。而后didSelectRow则是对应选择后的数据操做。具体的作法和UITableView
差很少。下面给出一个参考实现,注意,涉及iOS时间操做的API本文很少加阐述,请读者自行查阅API文档:ui
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { switch (component) { // component是栏目index,从0开始,后面的row也同样是从0开始 case 0: { // 第一栏为年,这里startDate和endDate为起始时间和截止时间,请自行指定 NSDateComponents *startCpts = [self.calendar components:NSYearCalendarUnit fromDate:self.startDate]; NSDateComponents *endCpts = [self.calendar components:NSYearCalendarUnit fromDate:self.endDate]; return [endCpts year] - [startCpts year] + 1; } case 1: // 第二栏为月份 return 12; case 2: { // 第三栏为对应月份的天数 NSRange dayRange = [self.calendar rangeOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate:self.selectedDate]; DLog(@"current month: %d, day number: %d", [[self.calendar components:NSMonthCalendarUnit fromDate:self.selectedDate] month], dayRange.length); return dayRange.length; } default: return 0; } } - (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view { UILabel *dateLabel = (UILabel *)view; if (!dataLabel) { dataLabel = [[UILabel alloc] init]; [dateLabel setFont:self.font]; [dateLabel setTextColor:self.fontColor]; [dateLabel setBackgroundColor:[UIColor clearColor]]; } switch (component) { case 0: { NSDateComponents *components = [self.calendar components:NSYearCalendarUnit fromDate:self.startDate]; NSString *currentYear = [NSString stringWithFormat:@"%d", [components year] + row]; [dateLabel setText:currentYear]; dateLabel.textAlignment = NSTextAlignmentRight; break; } case 1: { // 返回月份能够用DateFormatter,这样能够支持本地化 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.locale = [NSLocale currentLocale]; NSArray *monthSymbols = [formatter monthSymbols]; [dateLabel setText:[monthSymbols objectAtIndex:row]]; dateLabel.textAlignment = NSTextAlignmentCenter; break; } case 2: { NSRange dateRange = [self.calendar rangeOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate:self.selectedDate]; NSString *currentDay = [NSString stringWithFormat:@"%02d", (row + 1) % (dateRange.length + 1)]; [dateLabel setText:currentDay]; dateLabel.textAlignment = NSTextAlignmentLeft; break; } default: break; } return dateLabel; } - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { NSInteger unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit; switch (component) { case 0: { NSDateComponents *indicatorComponents = [self.calendar components:NSYearCalendarUnit fromDate:self.startDate]; NSInteger year = [indicatorComponents year] + row; NSDateComponents *targetComponents = [self.calendar components:unitFlags fromDate:self.selectedDate]; [targetComponents setYear:year]; self.selectedDateComponets = targetComponents; [pickerView selectRow:0 inComponent:1 animated:YES]; break; } case 1: { NSDateComponents *targetComponents = [self.calendar components:unitFlags fromDate:self.selectedDate]; [targetComponents setMonth:row + 1]; self.selectedDateComponets = targetComponents; [pickerView selectRow:0 inComponent:2 animated:YES]; break; } case 2: { NSDateComponents *targetComponents = [self.calendar components:unitFlags fromDate:self.selectedDate]; [targetComponents setDay:row + 1]; self.selectedDateComponets = targetComponents; break; } default: break; } [pickerView reloadAllComponents]; // 注意,这一句不能掉,不然选择后每一栏的数据不会重载,其做用与UITableView中的reloadData类似 }
另外,若是但愿对操控事件进行处理,咱们建立的类能够继承UIControl,并实现sendAction:to:forEvent:
方法。若是没有这个需求,直接继承NSObject就好了。设计
若是须要修改控件的背景材质,能够替换UIPickerView
的index为2的subview。code