作應用的時候免不了會對某些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