支持四種格式,可控制可選時間范圍,如下:
(1)年月日時分
(2)年月日
(3)月日時分
(4)時分
效果如下:
iPhone 5s
iPhone 6
iPhone 6 Plus
ViewController.h
#import <UIKit/UIKit.h> #import "KMDatePicker.h" @interface ViewController : UIViewController <UITextFieldDelegate, KMDatePickerDelegate> @property (strong, nonatomic) UITextField *txtFCurrent; @property (strong, nonatomic) IBOutlet UITextField *txtFYearMonthDayHourMinute; @property (strong, nonatomic) IBOutlet UITextField *txtFMonthDayHourMinute; @property (strong, nonatomic) IBOutlet UITextField *txtFYearMonthDay; @property (strong, nonatomic) IBOutlet UITextField *txtFHourMinute; @property (strong, nonatomic) IBOutlet UITextField *txtFLimitedDate; @end
ViewController.m
#import "ViewController.h" #import "DateHelper.h" #import "NSDate+CalculateDay.h" @interface ViewController () - (void)layoutUI; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self layoutUI]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)layoutUI { CGRect rect = [[UIScreen mainScreen] bounds]; rect = CGRectMake(0.0, 0.0, rect.size.width, 216.0); //年月日時分 KMDatePicker *datePicker = [[KMDatePicker alloc] initWithFrame:rect delegate:self datePickerStyle:KMDatePickerStyleYearMonthDayHourMinute]; _txtFYearMonthDayHourMinute.inputView = datePicker; _txtFYearMonthDayHourMinute.delegate = self; //年月日 datePicker = [[KMDatePicker alloc] initWithFrame:rect delegate:self datePickerStyle:KMDatePickerStyleYearMonthDay]; _txtFYearMonthDay.inputView = datePicker; _txtFYearMonthDay.delegate = self; //月日時分 datePicker = [[KMDatePicker alloc] initWithFrame:rect delegate:self datePickerStyle:KMDatePickerStyleMonthDayHourMinute]; _txtFMonthDayHourMinute.inputView = datePicker; _txtFMonthDayHourMinute.delegate = self; //時分 datePicker = [[KMDatePicker alloc] initWithFrame:rect delegate:self datePickerStyle:KMDatePickerStyleHourMinute]; _txtFHourMinute.inputView = datePicker; _txtFHourMinute.delegate = self; //年月日時分;限制時間范圍 datePicker = [[KMDatePicker alloc] initWithFrame:rect delegate:self datePickerStyle:KMDatePickerStyleYearMonthDayHourMinute]; datePicker.minLimitedDate = [[DateHelper localeDate] addMonthAndDay:-24 days:0]; datePicker.maxLimitedDate = [datePicker.minLimitedDate addMonthAndDay:48 days:0]; _txtFLimitedDate.inputView = datePicker; _txtFLimitedDate.delegate = self; } - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self.view endEditing:YES]; } #pragma mark - UITextFieldDelegate - (void)textFieldDidBeginEditing:(UITextField *)textField { _txtFCurrent = textField; } #pragma mark - KMDatePickerDelegate - (void)datePicker:(KMDatePicker *)datePicker didSelectDate:(KMDatePickerDateModel *)datePickerDate { NSString *dateStr = [NSString stringWithFormat:@"%@-%@-%@ %@:%@ %@", datePickerDate.year, datePickerDate.month, datePickerDate.day, datePickerDate.hour, datePickerDate.minute, datePickerDate.weekdayName ]; _txtFCurrent.text = dateStr; } @end
Main.storyboard
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8191" systemVersion="15A284" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r"> <dependencies> <deployment identifier="iOS"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8154"/> </dependencies> <scenes> <!--View Controller--> <scene sceneID="tne-QT-ifu"> <objects> <viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController"> <layoutGuides> <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/> <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> </layoutGuides> <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> <rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" border placeholder="YearMonthDayHourMinute" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="RU4-aD-bI3"> <rect key="frame" x="180" y="58" width="240" height="30"/> <animations/> <constraints> <constraint firstAttribute="width" constant="240" id="9Oi-OR-Ru9"/> <constraint firstAttribute="height" constant="30" id="Wsa-Pd-EDg"/> </constraints> <fontDescription key="fontDescription" type="system" pointSize="14"/> <textInputTraits key="textInputTraits"/> </textField> <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" border placeholder="MonthDayHourMinute" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="KFz-2Q-yIQ"> <rect key="frame" x="180" y="170" width="240" height="30"/> <animations/> <constraints> <constraint firstAttribute="height" constant="30" id="Qxz-uf-Ngd"/> <constraint firstAttribute="width" constant="240" id="pTd-xH-c6N"/> </constraints> <fontDescription key="fontDescription" type="system" pointSize="14"/> <textInputTraits key="textInputTraits"/> </textField> <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" border placeholder="YearMonthDay" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="V6m-6E-ZuV"> <rect key="frame" x="180" y="113" width="240" height="30"/> <animations/> <constraints> <constraint firstAttribute="height" constant="30" id="CzE-ap-TTQ"/> <constraint firstAttribute="width" constant="240" id="eCi-kT-2tg"/> </constraints> <fontDescription key="fontDescription" type="system" pointSize="14"/> <textInputTraits key="textInputTraits"/> </textField> <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" border placeholder="HourMinute" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Neu-Vj-fG0"> <rect key="frame" x="180" y="228" width="240" height="30"/> <animations/> <constraints> <constraint firstAttribute="width" constant="240" id="XBx-GA-ad2"/> <constraint firstAttribute="height" constant="30" id="tON-2M-EMP"/> </constraints> <fontDescription key="fontDescription" type="system" pointSize="14"/> <textInputTraits key="textInputTraits"/> </textField> <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" border placeholder="LimitedDate" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="vt1-Aj-i6Y"> <rect key="frame" x="180" y="280" width="240" height="30"/> <animations/> <constraints> <constraint firstAttribute="height" constant="30" id="QSC-Me-tpx"/> <constraint firstAttribute="width" constant="240" id="X0a-1i-Z2T"/> </constraints> <fontDescription key="fontDescription" type="system" pointSize="14"/> <textInputTraits key="textInputTraits"/> </textField> </subviews> <animations/> <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> <constraints> <constraint firstItem="Neu-Vj-fG0" firstAttribute="top" secondItem="KFz-2Q-yIQ" secondAttribute="bottom" constant="28" id="4Wp-rC-NJF"/> <constraint firstItem="RU4-aD-bI3" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="38" id="EU7-1f-tVl"/> <constraint firstItem="RU4-aD-bI3" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="FUG-e6-Qhi"/> <constraint firstItem="Neu-Vj-fG0" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="HRH-7N-oaY"/> <constraint firstItem="V6m-6E-ZuV" firstAttribute="top" secondItem="RU4-aD-bI3" secondAttribute="bottom" constant="25" id="Ind-TZ-Rzm"/> <constraint firstItem="V6m-6E-ZuV" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="JTW-zd-LlI"/> <constraint firstItem="KFz-2Q-yIQ" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="LpF-Jq-wwJ"/> <constraint firstItem="KFz-2Q-yIQ" firstAttribute="top" secondItem="V6m-6E-ZuV" secondAttribute="bottom" constant="27" id="TyY-DA-rn2"/> <constraint firstItem="vt1-Aj-i6Y" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="g0Z-bZ-p3g"/> <constraint firstItem="vt1-Aj-i6Y" firstAttribute="top" secondItem="Neu-Vj-fG0" secondAttribute="bottom" constant="22" id="tyA-YZ-slJ"/> </constraints> </view> <connections> <outlet property="txtFHourMinute" destination="Neu-Vj-fG0" id="wPd-UK-nh5"/> <outlet property="txtFLimitedDate" destination="vt1-Aj-i6Y" id="MzT-tx-thY"/> <outlet property="txtFMonthDayHourMinute" destination="KFz-2Q-yIQ" id="Hp6-Ve-tYI"/> <outlet property="txtFYearMonthDay" destination="V6m-6E-ZuV" id="0cJ-v8-xh3"/> <outlet property="txtFYearMonthDayHourMinute" destination="RU4-aD-bI3" id="neo-JQ-wV5"/> </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> </objects> </scene> </scenes> </document>
NSDate+CalculateDay.h
#import <Foundation/Foundation.h> @interface NSDate (CalculateDay) /** * 獲取對應月份的天數 * * @return 對應月份的天數 */ - (NSUInteger)daysOfMonth; /** * 獲取對應年份的天數 * * @return 對應年份的天數 */ - (NSUInteger)daysOfYear; /** * 獲取對應月份的第一天時間 * * @return 對應月份的第一天時間 */ - (NSDate *)firstDayOfMonth; /** * 獲取對應月份的最後一天時間 * * @return 對應月份的最後一天時間 */ - (NSDate *)lastDayOfMonth; /** * 根據月數和天數間隔,獲取間隔後的時間 * * @param months 月數間隔 * @param days 天數間隔 * * @return 間隔後的時間 */ - (NSDate *)addMonthAndDay:(NSUInteger)months days:(NSUInteger)days; /** * 根據開始時間和結束時間,獲取間隔的時間數組 * * @param toDate 結束時間 * * @return 間隔的時間數組(月數和天數;toDate-fromDate的比較值是有符號整數NSInteger,所以存在負數的可能) */ - (NSArray *)monthAndDayBetweenTwoDates:(NSDate *)toDate; /** * 獲取對應周期的數字 * * @return 對應周期的數字(1:周日、2:周一、3:周二、4:周三、5:周四、6:周五、7:周六) */ - (NSInteger)weekday; /** * 根據地區標示符,獲取對應周期的名稱 * * @param isShortName 是否是短名稱 * @param localeIdentifier 地區標示符(中國:zh_CN、美國:en_US) * * @return 對應周期的名稱 */ - (NSString *)weekdayName:(BOOL)isShortName localeIdentifier:(NSString *)localeIdentifier; /** * 獲取對應周期的中文名稱 * * @param isShortName 是否是短名稱 * * @return 對應周期的中文名稱(短名稱:周日、周一、周二、周三、周四、周五、周六)(長名稱:星期日、星期一、星期二、星期三、星期四、星期五、星期六) */ - (NSString *)weekdayNameCN:(BOOL)isShortName; /** * 獲取對應周期的英文名稱 * * @param isShortName 是否是短名稱 * * @return 對應周期的英文名稱(短名稱:Sun、Mon、Tue、Wed、Thu、Fri、Sat)(長名稱:Sunday、Monday、Tuesday、Wednesday、Thursday、Friday、Saturday) */ - (NSString *)weekdayNameEN:(BOOL)isShortName; @end
NSDate+CalculateDay.m
#import "NSDate+CalculateDay.h" @implementation NSDate (CalculateDay) - (NSUInteger)daysOfMonth { NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSRange range = [gregorian rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:self]; return range.length; } - (NSUInteger)daysOfYear { NSUInteger days = 0; NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSDateComponents *comps = [gregorian components:NSCalendarUnitYear fromDate:self]; for (NSUInteger i=1; i<=12; i++) { [comps setMonth:i]; days += [[gregorian dateFromComponents:comps] daysOfMonth]; } return days; } - (NSDate *)firstDayOfMonth { NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; unsigned unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; NSDateComponents *comps = [gregorian components:unitFlags fromDate:self]; [comps setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; //使用UTC或GMT解決時區相差8小時的問題 [comps setDay:1]; return [gregorian dateFromComponents:comps]; } - (NSDate *)lastDayOfMonth { NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; unsigned unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; NSDateComponents *comps = [gregorian components:unitFlags fromDate:self]; [comps setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; //使用UTC或GMT解決時區相差8小時的問題 [comps setDay:[self daysOfMonth]]; return [gregorian dateFromComponents:comps]; } - (NSDate *)addMonthAndDay:(NSUInteger)months days:(NSUInteger)days { NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSDateComponents *comps = [NSDateComponents new]; [comps setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; //使用UTC或GMT解決時區相差8小時的問題 [comps setMonth:months]; [comps setDay:days]; return [gregorian dateByAddingComponents:comps toDate:self options:0]; } - (NSArray *)monthAndDayBetweenTwoDates:(NSDate *)toDate { NSMutableArray *mArrMonthAndDay = [[NSMutableArray alloc] initWithCapacity:2]; NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; unsigned unitFlags = NSCalendarUnitMonth | NSCalendarUnitDay; NSDateComponents *comps = [gregorian components:unitFlags fromDate:self toDate:toDate options:0]; [mArrMonthAndDay addObject:[NSString stringWithFormat:@"%ld", (long)[comps month]]]; [mArrMonthAndDay addObject:[NSString stringWithFormat:@"%ld", (long)[comps day]]]; return mArrMonthAndDay; } - (NSInteger)weekday { return [[[NSCalendar currentCalendar] components:NSCalendarUnitWeekday fromDate:self] weekday]; } - (NSString *)weekdayName:(BOOL)isShortName localeIdentifier:(NSString *)localeIdentifier { NSDateFormatter *formatter = [NSDateFormatter new]; formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:localeIdentifier]; formatter.dateFormat = isShortName ? @"EE" : @"EEEE"; return [formatter stringFromDate:self]; } - (NSString *)weekdayNameCN:(BOOL)isShortName { /* //方法一: NSArray *arrWeekdayName = isShortName ? @[ @"周日", @"周一", @"周二", @"周三", @"周四", @"周五", @"周六" ] : @[ @"星期日", @"星期一", @"星期二", @"星期三", @"星期四", @"星期五", @"星期六" ]; NSInteger weekday = [self weekday]; return arrWeekdayName[weekday - 1]; */ //方法二: return [self weekdayName:isShortName localeIdentifier:@"zh_CN"]; } - (NSString *)weekdayNameEN:(BOOL)isShortName { return [self weekdayName:isShortName localeIdentifier:@"en_US"]; } @end
DateHelper.h
#import <Foundation/Foundation.h> @interface DateHelper : NSObject /** * 獲取本地當前時間 * * @return 本地當前時間 */ + (NSDate *)localeDate; /** * 根據時間字符串和其格式,獲取對應的時間 * * @param dateStr 時間字符串 * @param format 時間字符串格式(默認值為@"yyyy-MM-dd HH:mm") * * @return 對應的時間 */ + (NSDate *)dateFromString:(NSString *)dateStr withFormat:(NSString *)format; @end
DateHelper.m
#import "DateHelper.h" @implementation DateHelper + (NSDate *)localeDate { NSDate *date = [NSDate date]; NSTimeZone *zone = [NSTimeZone systemTimeZone]; NSInteger interval = [zone secondsFromGMTForDate:date]; return [date dateByAddingTimeInterval:interval]; } + (NSDate *)dateFromString:(NSString *)dateStr withFormat:(NSString *)format { NSDateFormatter *formatter = [NSDateFormatter new]; formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; formatter.dateFormat = format ?: @"yyyy-MM-dd HH:mm"; return [formatter dateFromString:dateStr]; } @end
KMDatePickerDateModel.h
#import <Foundation/Foundation.h> @interface KMDatePickerDateModel : NSObject @property (copy, nonatomic) NSString *year; @property (copy, nonatomic) NSString *month; @property (copy, nonatomic) NSString *day; @property (copy, nonatomic) NSString *hour; @property (copy, nonatomic) NSString *minute; @property (copy, nonatomic) NSString *weekdayName; - (instancetype)initWithDate:(NSDate *)date; @end
KMDatePickerDateModel.m
#import "KMDatePickerDateModel.h" #import "NSDate+CalculateDay.h" @implementation KMDatePickerDateModel - (instancetype)initWithDate:(NSDate *)date { if (self = [super init]) { NSDateFormatter *formatter = [NSDateFormatter new]; formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; formatter.dateFormat = @"yyyyMMddHHmm"; NSString *dateStr = [formatter stringFromDate:date]; _year = [dateStr substringWithRange:NSMakeRange(0, 4)]; _month = [dateStr substringWithRange:NSMakeRange(4, 2)]; _day = [dateStr substringWithRange:NSMakeRange(6, 2)]; _hour = [dateStr substringWithRange:NSMakeRange(8, 2)]; _minute = [dateStr substringWithRange:NSMakeRange(10, 2)]; _weekdayName = [date weekdayNameCN:YES]; } return self; } @end
KMDatePicker.h
#import <UIKit/UIKit.h> #import "KMDatePickerDateModel.h" typedef NS_ENUM(NSUInteger, KMDatePickerStyle) { KMDatePickerStyleYearMonthDayHourMinute, KMDatePickerStyleYearMonthDay, KMDatePickerStyleMonthDayHourMinute, KMDatePickerStyleHourMinute }; @protocol KMDatePickerDelegate; @interface KMDatePicker : UIView <UIPickerViewDataSource, UIPickerViewDelegate> @property (weak, nonatomic) id<KMDatePickerDelegate> delegate; @property (assign, nonatomic) KMDatePickerStyle datePickerStyle; @property (strong, nonatomic) NSDate *minLimitedDate; //最小限制時間;默認值為1970-01-01 00:00 @property (strong, nonatomic) NSDate *maxLimitedDate; //最大限制時間;默認值為2060-12-31 23:59 @property (strong, nonatomic) NSDate *scrollToDate; //滾動到指定時間;默認值為當前時間 - (instancetype)initWithFrame:(CGRect)frame delegate:(id<KMDatePickerDelegate>)delegate datePickerStyle:(KMDatePickerStyle)datePickerStyle; @end @protocol KMDatePickerDelegate <NSObject> @required - (void)datePicker:(KMDatePicker *)datePicker didSelectDate:(KMDatePickerDateModel *)datePickerDate; @end
KMDatePicker.m
#import "KMDatePicker.h" #import "NSDate+CalculateDay.h" #import "DateHelper.h" #define kDefaultMinLimitedDate @"1970-01-01 00:00" #define kDefaultMaxLimitedDate @"2060-12-31 23:59" #define kMonthCountOfEveryYear 12 #define kHourCountOfEveryDay 24 #define kMinuteCountOfEveryHour 60 #define kRowDisabledStatusColor [UIColor redColor] #define kRowNormalStatusColor [UIColor blackColor] #define kWidthOfTotal self.frame.size.width #define kHeightOfButtonContentView 35.0 #define kButtonNormalStatusColor [UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:1.0] @interface KMDatePicker () { UIPickerView *pikV; //最小和最大限制時間、滾動到指定時間實體對象實例 KMDatePickerDateModel *datePickerDateMinLimited; KMDatePickerDateModel *datePickerDateMaxLimited; KMDatePickerDateModel *datePickerDateScrollTo; //存儲時間數據源的數組 NSMutableArray *mArrYear; NSMutableArray *mArrMonth; NSMutableArray *mArrDay; NSMutableArray *mArrHour; NSMutableArray *mArrMinute; //時間數據源的數組中,選中元素的索引 NSInteger yearIndex; NSInteger monthIndex; NSInteger dayIndex; NSInteger hourIndex; NSInteger minuteIndex; } - (void)addUnitLabel:(NSString *)text withPointX:(CGFloat)pointX; - (NSUInteger)daysOfMonth; - (void)reloadDayArray; - (void)loadData; - (void)scrollToDateIndexPosition; - (void)playDelegateAfterSelectedRow; - (BOOL)validatedDate:(NSDate *)date; - (BOOL)canShowScrollToNowButton; - (void)cancel:(UIButton *)sender; - (void)scrollToNowDateIndexPosition:(UIButton *)sender; - (void)confirm:(UIButton *)sender; - (UIColor *)monthRowTextColor:(NSInteger)row; - (UIColor *)dayRowTextColor:(NSInteger)row; - (UIColor *)hourRowTextColor:(NSInteger)row; - (UIColor *)minuteRowTextColor:(NSInteger)row; @end @implementation KMDatePicker - (instancetype)init { if (self = [super init]) { self.backgroundColor = [UIColor whiteColor]; } return self; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.backgroundColor = [UIColor whiteColor]; } return self; } - (instancetype)initWithFrame:(CGRect)frame delegate:(id<KMDatePickerDelegate>)delegate datePickerStyle:(KMDatePickerStyle)datePickerStyle { _delegate = delegate; _datePickerStyle = datePickerStyle; return [self initWithFrame:frame]; } #pragma mark - 自定義方法 - (void)addUnitLabel:(NSString *)text withPointX:(CGFloat)pointX { CGFloat pointY = pikV.frame.size.height/2 - 10.0 + kHeightOfButtonContentView; UILabel *lblUnit = [[UILabel alloc] initWithFrame:CGRectMake(pointX, pointY, 20.0, 20.0)]; lblUnit.text = text; lblUnit.textAlignment = NSTextAlignmentCenter; lblUnit.textColor = [UIColor blackColor]; lblUnit.backgroundColor = [UIColor clearColor]; lblUnit.font = [UIFont systemFontOfSize:18.0]; lblUnit.layer.shadowColor = [[UIColor whiteColor] CGColor]; lblUnit.layer.shadowOpacity = 0.5; lblUnit.layer.shadowRadius = 5.0; [self addSubview:lblUnit]; } - (NSUInteger)daysOfMonth { NSString *dateStr = [NSString stringWithFormat:@"%@-%@-01 00:00", mArrYear[yearIndex], mArrMonth[monthIndex]]; return [[DateHelper dateFromString:dateStr withFormat:@"yyyy-MM-dd HH:mm"] daysOfMonth]; } - (void)reloadDayArray { mArrDay = [NSMutableArray new]; for (NSUInteger i=1, len=[self daysOfMonth]; i<=len; i++) { [mArrDay addObject:[NSString stringWithFormat:@"%02ld", (long)i]]; } } - (void)loadData { //初始化最小和最大限制時間、滾動到指定時間實體對象實例 if (!_minLimitedDate) { _minLimitedDate = [DateHelper dateFromString:kDefaultMinLimitedDate withFormat:nil]; } datePickerDateMinLimited = [[KMDatePickerDateModel alloc] initWithDate:_minLimitedDate]; if (!_maxLimitedDate) { _maxLimitedDate = [DateHelper dateFromString:kDefaultMaxLimitedDate withFormat:nil]; } datePickerDateMaxLimited = [[KMDatePickerDateModel alloc] initWithDate:_maxLimitedDate]; //滾動到指定時間;默認值為當前時間。如果是使用自定義時間小於最小限制時間,這時就以最小限制時間為准;如果是使用自定義時間大於最大限制時間,這時就以最大限制時間為准 if (!_scrollToDate) { _scrollToDate = [DateHelper localeDate]; } if ([_scrollToDate compare:_minLimitedDate] == NSOrderedAscending) { _scrollToDate = _minLimitedDate; } else if ([_scrollToDate compare:_maxLimitedDate] == NSOrderedDescending) { _scrollToDate = _maxLimitedDate; } datePickerDateScrollTo = [[KMDatePickerDateModel alloc] initWithDate:_scrollToDate]; //初始化存儲時間數據源的數組 //年 mArrYear = [NSMutableArray new]; for (NSInteger beginVal=[datePickerDateMinLimited.year integerValue], endVal=[datePickerDateMaxLimited.year integerValue]; beginVal<=endVal; beginVal++) { [mArrYear addObject:[NSString stringWithFormat:@"%ld", (long)beginVal]]; } yearIndex = [datePickerDateScrollTo.year integerValue] - [datePickerDateMinLimited.year integerValue]; //月 mArrMonth = [[NSMutableArray alloc] initWithCapacity:kMonthCountOfEveryYear]; for (NSInteger i=1; i<=kMonthCountOfEveryYear; i++) { [mArrMonth addObject:[NSString stringWithFormat:@"%02ld", (long)i]]; } monthIndex = [datePickerDateScrollTo.month integerValue] - 1; //日 [self reloadDayArray]; dayIndex = [datePickerDateScrollTo.day integerValue] - 1; //時 mArrHour = [[NSMutableArray alloc] initWithCapacity:kHourCountOfEveryDay]; for (NSInteger i=0; i<kHourCountOfEveryDay; i++) { [mArrHour addObject:[NSString stringWithFormat:@"%02ld", (long)i]]; } hourIndex = [datePickerDateScrollTo.hour integerValue]; //分 mArrMinute = [[NSMutableArray alloc] initWithCapacity:kMinuteCountOfEveryHour]; for (NSInteger i=0; i<kMinuteCountOfEveryHour; i++) { [mArrMinute addObject:[NSString stringWithFormat:@"%02ld", (long)i]]; } minuteIndex = [datePickerDateScrollTo.minute integerValue]; } - (void)scrollToDateIndexPosition { NSArray *arrIndex; switch (_datePickerStyle) { case KMDatePickerStyleYearMonthDayHourMinute: { arrIndex = @[ [NSNumber numberWithInteger:yearIndex], [NSNumber numberWithInteger:monthIndex], [NSNumber numberWithInteger:dayIndex], [NSNumber numberWithInteger:hourIndex], [NSNumber numberWithInteger:minuteIndex] ]; break; } case KMDatePickerStyleYearMonthDay: { arrIndex = @[ [NSNumber numberWithInteger:yearIndex], [NSNumber numberWithInteger:monthIndex], [NSNumber numberWithInteger:dayIndex] ]; break; } case KMDatePickerStyleMonthDayHourMinute: { arrIndex = @[ [NSNumber numberWithInteger:monthIndex], [NSNumber numberWithInteger:dayIndex], [NSNumber numberWithInteger:hourIndex], [NSNumber numberWithInteger:minuteIndex] ]; break; } case KMDatePickerStyleHourMinute: { arrIndex = @[ [NSNumber numberWithInteger:hourIndex], [NSNumber numberWithInteger:minuteIndex] ]; break; } } for (NSUInteger i=0, len=arrIndex.count; i<len; i++) { [pikV selectRow:[arrIndex[i] integerValue] inComponent:i animated:YES]; } } - (BOOL)validatedDate:(NSDate *)date { NSString *minDateStr = [NSString stringWithFormat:@"%@-%@-%@ %@:%@", datePickerDateMinLimited.year, datePickerDateMinLimited.month, datePickerDateMinLimited.day, datePickerDateMinLimited.hour, datePickerDateMinLimited.minute ]; return !([date compare:[DateHelper dateFromString:minDateStr withFormat:nil]] == NSOrderedAscending || [date compare:_maxLimitedDate] == NSOrderedDescending); } - (BOOL)canShowScrollToNowButton { return [self validatedDate:[DateHelper localeDate]]; } - (void)cancel:(UIButton *)sender { UIViewController *delegateVC = (UIViewController *)self.delegate; [delegateVC.view endEditing:YES]; } - (void)scrollToNowDateIndexPosition:(UIButton *)sender { //為了區別最大最小限制范圍外行的標簽顏色,這裡需要重新加載所有組件列 [pikV reloadAllComponents]; _scrollToDate = [DateHelper localeDate]; datePickerDateScrollTo = [[KMDatePickerDateModel alloc] initWithDate:_scrollToDate]; yearIndex = [datePickerDateScrollTo.year integerValue] - [datePickerDateMinLimited.year integerValue]; monthIndex = [datePickerDateScrollTo.month integerValue] - 1; dayIndex = [datePickerDateScrollTo.day integerValue] - 1; hourIndex = [datePickerDateScrollTo.hour integerValue]; minuteIndex = [datePickerDateScrollTo.minute integerValue]; [self scrollToDateIndexPosition]; } - (void)confirm:(UIButton *)sender { [self playDelegateAfterSelectedRow]; [self cancel:sender]; } - (UIColor *)monthRowTextColor:(NSInteger)row { UIColor *color = kRowNormalStatusColor; NSString *dateStr = [NSString stringWithFormat:@"%@-%@-01 00:00", mArrYear[yearIndex], mArrMonth[row] ]; NSDate *date = [DateHelper dateFromString:dateStr withFormat:nil]; NSString *minDateStr = [NSString stringWithFormat:@"%@-%@-01 00:00", datePickerDateMinLimited.year, datePickerDateMinLimited.month ]; if ([date compare:[DateHelper dateFromString:minDateStr withFormat:nil]] == NSOrderedAscending || [date compare:_maxLimitedDate] == NSOrderedDescending) { color = kRowDisabledStatusColor; } return color; } - (UIColor *)dayRowTextColor:(NSInteger)row { UIColor *color = kRowNormalStatusColor; NSString *dateStr = [NSString stringWithFormat:@"%@-%@-%@ 00:00", mArrYear[yearIndex], mArrMonth[monthIndex], mArrDay[row] ]; NSDate *date = [DateHelper dateFromString:dateStr withFormat:nil]; NSString *minDateStr = [NSString stringWithFormat:@"%@-%@-%@ 00:00", datePickerDateMinLimited.year, datePickerDateMinLimited.month, datePickerDateMinLimited.day ]; if ([date compare:[DateHelper dateFromString:minDateStr withFormat:nil]] == NSOrderedAscending || [date compare:_maxLimitedDate] == NSOrderedDescending) { color = kRowDisabledStatusColor; } return color; } - (UIColor *)hourRowTextColor:(NSInteger)row { UIColor *color = kRowNormalStatusColor; NSString *dateStr = [NSString stringWithFormat:@"%@-%@-%@ %@:00", mArrYear[yearIndex], mArrMonth[monthIndex], mArrDay[dayIndex], mArrHour[row] ]; NSDate *date = [DateHelper dateFromString:dateStr withFormat:nil]; NSString *minDateStr = [NSString stringWithFormat:@"%@-%@-%@ %@:00", datePickerDateMinLimited.year, datePickerDateMinLimited.month, datePickerDateMinLimited.day, datePickerDateMinLimited.hour ]; if ([date compare:[DateHelper dateFromString:minDateStr withFormat:nil]] == NSOrderedAscending || [date compare:_maxLimitedDate] == NSOrderedDescending) { color = kRowDisabledStatusColor; } return color; } - (UIColor *)minuteRowTextColor:(NSInteger)row { NSString *format = @"yyyy-MM-dd HH:mm:ss"; UIColor *color = kRowNormalStatusColor; NSString *dateStr = [NSString stringWithFormat:@"%@-%@-%@ %@:%@:00", mArrYear[yearIndex], mArrMonth[monthIndex], mArrDay[dayIndex], mArrHour[hourIndex], mArrMinute[row] ]; NSDate *date = [DateHelper dateFromString:dateStr withFormat:format]; NSString *minDateStr = [NSString stringWithFormat:@"%@-%@-%@ %@:%@:00", datePickerDateMinLimited.year, datePickerDateMinLimited.month, datePickerDateMinLimited.day, datePickerDateMinLimited.hour, datePickerDateMinLimited.minute ]; if ([date compare:[DateHelper dateFromString:minDateStr withFormat:format]] == NSOrderedAscending || [date compare:_maxLimitedDate] == NSOrderedDescending) { color = kRowDisabledStatusColor; } return color; } #pragma mark - 繪制內容 - (void)drawRect:(CGRect)rect { //加載數據 [self loadData]; //初始化頭部按鈕(取消、現在時間、確定) UIView *buttonContentView = [[UIView alloc] initWithFrame:CGRectMake(-2.0, 0.0, kWidthOfTotal + 4.0, kHeightOfButtonContentView)]; buttonContentView.layer.borderColor = [UIColor lightGrayColor].CGColor; buttonContentView.layer.borderWidth = 0.5; [self addSubview:buttonContentView]; UIButton *btnCancel = [UIButton buttonWithType:UIButtonTypeCustom]; btnCancel.frame = CGRectMake(2.0, 2.5, 60.0, kHeightOfButtonContentView - 5.0); btnCancel.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; [btnCancel setTitle:@"取消" forState:UIControlStateNormal]; [btnCancel setTitleColor:kButtonNormalStatusColor forState:UIControlStateNormal]; [btnCancel addTarget:self action:@selector(cancel:) forControlEvents:UIControlEventTouchUpInside]; [buttonContentView addSubview:btnCancel]; if ([self canShowScrollToNowButton]) { UIButton *btnScrollToNow = [UIButton buttonWithType:UIButtonTypeCustom]; btnScrollToNow.frame = CGRectMake(buttonContentView.frame.size.width/2 - 50.0, 2.5, 100.0, kHeightOfButtonContentView - 5.0); btnScrollToNow.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; [btnScrollToNow setTitle:@"現在時間" forState:UIControlStateNormal]; [btnScrollToNow setTitleColor:kButtonNormalStatusColor forState:UIControlStateNormal]; [btnScrollToNow addTarget:self action:@selector(scrollToNowDateIndexPosition:) forControlEvents:UIControlEventTouchUpInside]; [buttonContentView addSubview:btnScrollToNow]; } UIButton *btnConfirm = [UIButton buttonWithType:UIButtonTypeCustom]; btnConfirm.frame = CGRectMake(kWidthOfTotal - 58.0, 2.5, 60.0, kHeightOfButtonContentView - 5.0); btnConfirm.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; [btnConfirm setTitle:@"確定" forState:UIControlStateNormal]; [btnConfirm setTitleColor:kButtonNormalStatusColor forState:UIControlStateNormal]; [btnConfirm addTarget:self action:@selector(confirm:) forControlEvents:UIControlEventTouchUpInside]; [buttonContentView addSubview:btnConfirm]; //初始化選擇器視圖控件 if (!pikV) { pikV = [[UIPickerView alloc]initWithFrame:CGRectMake(0.0, kHeightOfButtonContentView, kWidthOfTotal, self.frame.size.height - kHeightOfButtonContentView)]; pikV.showsSelectionIndicator = YES; pikV.backgroundColor = [UIColor clearColor]; [self addSubview:pikV]; } pikV.dataSource = self; pikV.delegate = self; //初始化滾動到指定時間位置 [self scrollToDateIndexPosition]; } #pragma mark - 執行 KMDatePickerDelegate 委托代理協議方法,用於回調傳遞參數 - (void)playDelegateAfterSelectedRow { if ([self.delegate respondsToSelector:@selector(datePicker:didSelectDate:)]) { [self.delegate datePicker:self didSelectDate:datePickerDateScrollTo]; } } #pragma mark - UIPickerViewDataSource - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { NSInteger numberOfComponents = 0; switch (_datePickerStyle) { case KMDatePickerStyleYearMonthDayHourMinute: { numberOfComponents = 5; break; } case KMDatePickerStyleYearMonthDay: { numberOfComponents = 3; break; } case KMDatePickerStyleMonthDayHourMinute: { numberOfComponents = 4; break; } case KMDatePickerStyleHourMinute: { numberOfComponents = 2; break; } } return numberOfComponents; } - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { NSInteger numberOfRows = 0; switch (_datePickerStyle) { case KMDatePickerStyleYearMonthDayHourMinute: { switch (component) { case 0: numberOfRows = mArrYear.count; break; case 1: numberOfRows = kMonthCountOfEveryYear; break; case 2: numberOfRows = [self daysOfMonth]; break; case 3: numberOfRows = kHourCountOfEveryDay; break; case 4: numberOfRows = kMinuteCountOfEveryHour; break; } break; } case KMDatePickerStyleYearMonthDay: { switch (component) { case 0: numberOfRows = mArrYear.count; break; case 1: numberOfRows = kMonthCountOfEveryYear; break; case 2: numberOfRows = [self daysOfMonth]; break; } break; } case KMDatePickerStyleMonthDayHourMinute: { switch (component) { case 0: numberOfRows = kMonthCountOfEveryYear; break; case 1: numberOfRows = [self daysOfMonth]; break; case 2: numberOfRows = kHourCountOfEveryDay; break; case 3: numberOfRows = kMinuteCountOfEveryHour; break; } break; } case KMDatePickerStyleHourMinute: { switch (component) { case 0: numberOfRows = kHourCountOfEveryDay; break; case 1: numberOfRows = kMinuteCountOfEveryHour; break; } break; } } return numberOfRows; } #pragma mark - UIPickerViewDelegate - (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component { CGFloat width = 50.0; CGFloat widthOfAverage; switch (_datePickerStyle) { case KMDatePickerStyleYearMonthDayHourMinute: { //規則一:平均寬度 = (總共寬度 - 年份相比多兩個數字的寬度 - 分鐘會是右對齊導致需偏移寬度來顯示「分」這個文字) / 5等份 widthOfAverage = (kWidthOfTotal - 20.0 - 25.0) / 5; switch (component) { case 0: width = widthOfAverage + 20.0; //規則二:單位標簽的 X 坐標位置 = 列的水平居中 X 坐標 + 偏移量 [self addUnitLabel:@"年" withPointX:width/2 + 24.0]; break; case 1: width = widthOfAverage; [self addUnitLabel:@"月" withPointX:(widthOfAverage + 20.0) + width/2 + 16.0]; break; case 2: width = widthOfAverage; [self addUnitLabel:@"日" withPointX:(2*widthOfAverage + 20.0) + width/2 + 22.0]; break; case 3: width = widthOfAverage; [self addUnitLabel:@"時" withPointX:(3*widthOfAverage + 20.0) + width/2 + 28.0]; break; case 4: width = widthOfAverage; [self addUnitLabel:@"分" withPointX:(4*widthOfAverage + 20.0) + width/2 + 32.0]; break; } break; } case KMDatePickerStyleYearMonthDay: { widthOfAverage = (kWidthOfTotal - 20.0 - 15.0) / 3; switch (component) { case 0: width = widthOfAverage + 20.0; [self addUnitLabel:@"年" withPointX:width/2 + 24.0]; break; case 1: width = widthOfAverage; [self addUnitLabel:@"月" withPointX:(widthOfAverage + 20.0) + width/2 + 16.0]; break; case 2: width = widthOfAverage; [self addUnitLabel:@"日" withPointX:(2*widthOfAverage + 20.0) + width/2 + 22.0]; break; } break; } case KMDatePickerStyleMonthDayHourMinute: { widthOfAverage = (kWidthOfTotal - 20.0) / 4; switch (component) { case 0: width = widthOfAverage; [self addUnitLabel:@"月" withPointX:width/2 + 11.0]; break; case 1: width = widthOfAverage; [self addUnitLabel:@"日" withPointX:widthOfAverage + width/2 + 17.0]; break; case 2: width = widthOfAverage; [self addUnitLabel:@"時" withPointX:2*widthOfAverage + width/2 + 23.0]; break; case 3: width = widthOfAverage; [self addUnitLabel:@"分" withPointX:3*widthOfAverage + width/2 + 27.0]; break; } break; } case KMDatePickerStyleHourMinute: { widthOfAverage = (kWidthOfTotal - 10.0) / 2; switch (component) { case 0: width = widthOfAverage; [self addUnitLabel:@"時" withPointX:width/2 + 12.0]; break; case 1: width = widthOfAverage; [self addUnitLabel:@"分" withPointX:widthOfAverage + width/2 + 18.0]; break; } break; } } return width; } - (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component { return 30.0; } - (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(nullable UIView *)view { UILabel *lblCustom = (UILabel *)view; if (!lblCustom) { lblCustom = [UILabel new]; lblCustom.textAlignment = NSTextAlignmentCenter; lblCustom.font = [UIFont systemFontOfSize:18.0]; } NSString *text; UIColor *textColor = kRowNormalStatusColor; switch (_datePickerStyle) { case KMDatePickerStyleYearMonthDayHourMinute: { switch (component) { case 0: text = mArrYear[row]; break; case 1: text = mArrMonth[row]; textColor = [self monthRowTextColor:row]; break; case 2: text = mArrDay[row]; textColor = [self dayRowTextColor:row]; break; case 3: text = mArrHour[row]; textColor = [self hourRowTextColor:row]; break; case 4: text = mArrMinute[row]; textColor = [self minuteRowTextColor:row]; break; } break; } case KMDatePickerStyleYearMonthDay: { switch (component) { case 0: text = mArrYear[row]; break; case 1: text = mArrMonth[row]; textColor = [self monthRowTextColor:row]; break; case 2: text = mArrDay[row]; textColor = [self dayRowTextColor:row]; break; } break; } case KMDatePickerStyleMonthDayHourMinute: { switch (component) { case 0: text = mArrMonth[row]; textColor = [self monthRowTextColor:row]; break; case 1: text = mArrDay[row]; textColor = [self dayRowTextColor:row]; break; case 2: text = mArrHour[row]; textColor = [self hourRowTextColor:row]; break; case 3: text = mArrMinute[row]; textColor = [self minuteRowTextColor:row]; break; } break; } case KMDatePickerStyleHourMinute: { switch (component) { case 0: text = mArrHour[row]; textColor = [self hourRowTextColor:row]; break; case 1: text = mArrMinute[row]; textColor = [self minuteRowTextColor:row]; break; } break; } } lblCustom.text = text; lblCustom.textColor = textColor; return lblCustom; } - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { NSDate *scrollToDateTemp = _scrollToDate; KMDatePickerDateModel *datePickerDateScrollToTemp = datePickerDateScrollTo; switch (_datePickerStyle) { case KMDatePickerStyleYearMonthDayHourMinute: { switch (component) { case 0: yearIndex = row; break; case 1: monthIndex = row; break; case 2: dayIndex = row; break; case 3: hourIndex = row; break; case 4: minuteIndex = row; break; } if (component == 0 || component == 1) { [self reloadDayArray]; if (mArrDay.count-1 < dayIndex) { dayIndex = mArrDay.count-1; } //[pickerView reloadComponent:2]; } break; } case KMDatePickerStyleYearMonthDay: { switch (component) { case 0: yearIndex = row; break; case 1: monthIndex = row; break; case 2: dayIndex = row; break; } if (component == 0 || component == 1) { [self reloadDayArray]; if (mArrDay.count-1 < dayIndex) { dayIndex = mArrDay.count-1; } //[pickerView reloadComponent:2]; } break; } case KMDatePickerStyleMonthDayHourMinute: { switch (component) { case 0: monthIndex = row; break; case 1: dayIndex = row; break; case 2: hourIndex = row; break; case 3: minuteIndex = row; break; } if (component == 0) { [self reloadDayArray]; if (mArrDay.count-1 < dayIndex) { dayIndex = mArrDay.count-1; } //[pickerView reloadComponent:1]; } break; } case KMDatePickerStyleHourMinute: { switch (component) { case 0: hourIndex = row; break; case 1: minuteIndex = row; break; } break; } } NSString *dateStr = [NSString stringWithFormat:@"%@-%@-%@ %@:%@", mArrYear[yearIndex], mArrMonth[monthIndex], mArrDay[dayIndex], mArrHour[hourIndex], mArrMinute[minuteIndex] ]; _scrollToDate = [DateHelper dateFromString:dateStr withFormat:nil]; datePickerDateScrollTo = [[KMDatePickerDateModel alloc] initWithDate:_scrollToDate]; //為了區別最大最小限制范圍外行的標簽顏色,這裡需要重新加載所有組件列 [pickerView reloadAllComponents]; //如果選擇時間不在最小和最大限制時間范圍內就回滾 if (![self validatedDate:_scrollToDate]) { _scrollToDate = scrollToDateTemp; datePickerDateScrollTo = datePickerDateScrollToTemp; yearIndex = [datePickerDateScrollTo.year integerValue] - [datePickerDateMinLimited.year integerValue]; monthIndex = [datePickerDateScrollTo.month integerValue] - 1; dayIndex = [datePickerDateScrollTo.day integerValue] - 1; hourIndex = [datePickerDateScrollTo.hour integerValue]; minuteIndex = [datePickerDateScrollTo.minute integerValue]; [self scrollToDateIndexPosition]; } } @end