├── MXPickerView ├── Assets.xcassets │ ├── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── ViewController.h ├── AppDelegate.h ├── main.m ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── AppDelegate.m ├── MXAnimation │ ├── MXAnimation.h │ └── MXAnimation.m ├── MXPickerView │ ├── MXPickerView.h │ └── MXPickerView.m └── ViewController.m ├── MXPickerView.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── project.pbxproj ├── LICENSE ├── .gitignore └── README.md /MXPickerView/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MXPickerView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MXPickerView/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // MXPickerView 4 | // 5 | // Created by Michael on 2018/10/30. 6 | // Copyright © 2018 Michael. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /MXPickerView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MXPickerView.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /MXPickerView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // MXPickerView 4 | // 5 | // Created by Michael on 2018/10/30. 6 | // Copyright © 2018 Michael. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /MXPickerView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MXPickerView 4 | // 5 | // Created by Michael on 2018/10/30. 6 | // Copyright © 2018 Michael. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Michael 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MXPickerView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots/**/*.png 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /MXPickerView/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /MXPickerView/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /MXPickerView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // MXPickerView 4 | // 5 | // Created by Michael on 2018/10/30. 6 | // Copyright © 2018 Michael. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /MXPickerView/MXAnimation/MXAnimation.h: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+MXAnimation.h 3 | // Yunlu 4 | // 5 | // Created by Michael on 2018/9/6. 6 | // Copyright © 2018年 DCloud. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | #pragma mark - -------------------------------- CAAnimation (StartEndCallbacks) ------------------------------- 14 | @interface CAAnimation (StartEndCallbacks) 15 | 16 | @property (nonatomic, copy) void (^animationDidStartBlock)(CAAnimation *anim); 17 | 18 | @property (nonatomic, copy) void (^animationDidStopBlock)(CAAnimation *anim, BOOL finished); 19 | 20 | @end 21 | 22 | #pragma mark - -------------------------------- MXAnimation ------------------------------- 23 | @interface MXAnimation : NSObject 24 | 25 | #pragma mark - convenience animations 26 | + (CAKeyframeAnimation *)popOutScaleAnimationWithDuration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock; 27 | + (CAKeyframeAnimation *)popInScaleAnimationWithDuration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock; 28 | 29 | + (CAKeyframeAnimation *)upFromKeyWindowAnimationWithDuration:(NSTimeInterval)duration addToView:(UIView *)view updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock; 30 | + (CAKeyframeAnimation *)downToKeyWindowAnimationWithDuration:(NSTimeInterval)duration addToView:(UIView *)view updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock; 31 | 32 | + (CAKeyframeAnimation *)showAlphaAnimationWithDuration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock; 33 | + (CAKeyframeAnimation *)hideAlphaAnimationWithDuration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock; 34 | 35 | + (CAKeyframeAnimation *)deleteAnimationWithDuration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock; 36 | 37 | #pragma mark - basic animations 38 | + (CAKeyframeAnimation *)translateYAnimationWithValues:(NSArray *)values duration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock; 39 | 40 | + (CAKeyframeAnimation *)alphaAnimatonWithValues:(NSArray *)values duration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock; 41 | 42 | + (CAKeyframeAnimation *)scaleAnimatonWithValues:(NSArray *)values duration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /MXPickerView/MXPickerView/MXPickerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // MXPickerView.h 3 | // 1008 - MXPickerView 4 | // 5 | // Created by Michael on 2018/10/8. 6 | // Copyright © 2018 Michael. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef NS_ENUM(NSInteger, MXPickerViewMode) { 13 | MXPickerViewModeCustom, 14 | //mode for `UIPickerView` 15 | MXPickerViewModeDD, // 29号 16 | MXPickerViewModeDD_DD, // 29号 - 29号 17 | MXPickerViewModeMM, // 1月 18 | MXPickerViewModeMM_MM, // 12月 - 12月 19 | MXPickerViewModeMM_DD, // 12月 - 29号 20 | MXPickerViewModeMMDD_MMDD, // 12月 29号 - 12月 29号 21 | MXPickerViewModeYYYY, // 2018年 22 | MXPickerViewModeYYYY_YYYY, // 2018年 - 2018年 23 | MXPickerViewModeYYYY_MM, // 2018年 12月 24 | MXPickerViewModeYYYYMM_YYYYMM, // 2018年 12月 - 2018年 12月 25 | MXPickerViewModeYYYYMMDD_YYYYMMDD, // 2018年 12月 29日 - 2018年 12月 29日 26 | //mode for `UIDatePicker` 27 | MXPickerViewModeTime, // Displays hour, minute, and optionally AM/PM designation depending on the locale setting (e.g. 6 | 53 | PM) 28 | MXPickerViewModeDate, // Displays month, day, and year depending on the locale setting (e.g. November | 15 | 2007) 29 | MXPickerViewModeDateAndTime, // Displays date, hour, minute, and optionally AM/PM designation depending on the locale setting (e.g. Wed Nov 15 | 6 | 53 | PM) 30 | MXPickerViewModeCountDownTimer, // Displays hour and minute (e.g. 1 | 53) 31 | }; 32 | 33 | @interface MXPickerView : UIView 34 | 35 | /** 36 | * apperances for `UIBarButtonItem` in `MXPickerView` 37 | */ 38 | @property (nonatomic, copy) NSDictionary *barBtnItemTitleTextAttributes; 39 | 40 | /** 41 | * yearLocale e.g., 年 42 | * monthLocale e.g., 月 43 | * dayLocale e.g., 日 44 | */ 45 | @property (nonatomic, copy) NSString *yearLocale, *monthLocale, *dayLocale; 46 | 47 | /** 48 | * if `isToolBarPositionBottom` set to YES, toolBarBottom will postion bottom, cancel bar button item and done bar button item position center 49 | */ 50 | @property (nonatomic, getter=isToolBarPositionBottom) BOOL toolBarPositionBottom; 51 | 52 | /** 53 | * 不为`nil`时, `toolBarPositionBottom`会自动设置为`YES`,底部显示`返回`和`确定`按钮,顶部显示`topTipBarTitles`, 如`开始日期 - 结束日期` 54 | */ 55 | @property (nonatomic, copy) NSArray *topTipBarTitles; 56 | @property (nonatomic, readonly) MXPickerViewMode pickerViewMode; 57 | /** 58 | * both defalut is `44` 59 | */ 60 | @property (nonatomic) CGFloat topTipBarH, toolBarH; 61 | @property (nonatomic) NSMutableArray *modelsM; 62 | 63 | /** 64 | * default is nil, use for `MXPickerViewModeMM_MM`, `MXPickerViewModeDD_DD`, `MXPickerViewModeMMDD_MMDD`, `MXPickerViewModeYYYY_YYYY`, `MXPickerViewModeYYYYMM_YYYYMM`, `MXPickerViewModeYYYYMMDD_YYYYMMDD` 65 | e.g., ascending(1, 2, 3, 4, 5), descending(5, 4, 3, 2, 1) 66 | */ 67 | @property (nonatomic, getter=shouldShowDescending) BOOL showsDescending; 68 | 69 | /** 70 | * when mode is `MXPickerViewModeCustom`, `selectedTitleOrCustomModels` should pass `customModels`, type of `NSArray` 71 | * when mode is for date such as `MXPickerViewModeDD`..., `selectedTitleOrCustomModels` should pass `selectedTitle`, type of `NSString`, joined by whitespace, format something like `12 12` 72 | * when mode for `UIDatePicker`, such as `MXPickerViewModeDateAndTime`, `selectedTitleOrCustomModels` should not pass, always be nil! 73 | * 当模式为`MXPickerViewModeCustom`, `selectedTitleOrCustomModels` 应该为`customModels`, 是一个 `NSArray`数组类型 74 | * 当模式为日期一类的,如`MXPickerViewModeDD`..., `selectedTitleOrCustomModels`应该为`selectedTitle`, 是一个用空格拼接的`NSString`数组类型, 格式类似于`12 12` 75 | * 当模式为`UIDatePicker`一类的, 如`MXPickerViewModeDateAndTime`, `selectedTitleOrCustomModels` 不应该传, 默认值总是nil! 76 | */ 77 | @property (nonatomic) id selectedTitleOrCustomModels; 78 | 79 | /** 80 | * specify min/max date range. default is nil. When min > max, the values are ignored. Ignored in countdown timer mode 81 | */ 82 | @property (nonatomic) NSDate *minimumDate; 83 | 84 | /** 85 | * default is nil 86 | */ 87 | @property (nonatomic) NSDate *maximumDate; 88 | 89 | /** 90 | * `minimumDate` + `deltaBetweenMaxAndMin` = `maximumDate` 91 | */ 92 | @property (nonatomic) NSInteger deltaBetweenMaxAndMin; 93 | 94 | /** 95 | * each component's width height in picker view will set to `componentWidth` 96 | * 设置每个`component`的宽度,等宽,如果不设置,默认为`pickerView.bounds.size.width/numberOfComponents` 97 | */ 98 | @property (nonatomic) CGFloat componentWidth; 99 | 100 | /** 101 | * each component's row height in picker view will set to `componentRowHeight ` 102 | * 设置每个`component`的行高,如果不设置,默认为`40` 103 | */ 104 | @property (nonatomic) CGFloat componentRowHeight; 105 | 106 | /** 107 | * set component's width with different values 108 | * 为每个`component`设置不同的宽度, 相同的可以直接设置`pickerView.componentWidth = xx` 109 | */ 110 | @property (nonatomic, copy) CGFloat (^configPickerViewWidthForComponentBlock)(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component); 111 | 112 | /** 113 | * set component's row height with same values 114 | * 为每个`component`设置相同的行高, 等价于`pickerView.componentRowHeight = xx` 115 | */ 116 | @property (nonatomic, copy) CGFloat (^configPickerViewRowHeightForComponentBlock)(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component); 117 | 118 | /** 119 | * set content label text 120 | */ 121 | @property (nonatomic, copy) NSString * (^configPickerViewTitleForRowForComponentBlock)(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component); 122 | 123 | /** 124 | * set content label attributedTitle 125 | */ 126 | @property (nonatomic, copy) NSAttributedString * (^configPickerViewAttributedTitleForRowForComponentBlock)(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component); 127 | 128 | /** 129 | * set content label appearance such as font, alignment, backgroundColor 130 | * 设置 content label 的样式,如字体,文字对齐方式,背景颜色等... 131 | */ 132 | @property (nonatomic, copy) void (^configPickerViewContentLabelAppearanceForRowForComponentBlock)(MXPickerView *mxPickerView, UIPickerView *pickerView, UILabel *contentLabel, NSInteger row, NSInteger component); 133 | 134 | /** 135 | * content view for row in component 136 | * 自定义在`component`中的`contentView` 137 | */ 138 | @property (nonatomic, copy) UIView * (^configPickerViewViewForRowForComponentBlock)(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component, UIView *reusingView); 139 | 140 | //callbacks 141 | @property (nonatomic, copy) void (^datePickerDidChangeBlock)(MXPickerViewMode pickerViewMode, CGFloat countDownDuration, NSDate *date, NSString *selectedTitle); 142 | 143 | @property (nonatomic, copy) void (^doneButtonDidClickBlock)(MXPickerViewMode pickerViewMode, CGFloat countDownDuration, NSDate *date, id selectedTitleOrCustomModels); 144 | 145 | + (instancetype)showWithSize:(CGSize)size pickerViewMode:(MXPickerViewMode)pickerViewMode updateBlock:(void (^)(MXPickerView *pickerView, MXPickerViewMode pickerViewMode, id uiDatePickerOrUIPickerView))updateBlock; 146 | 147 | @end 148 | -------------------------------------------------------------------------------- /MXPickerView/MXAnimation/MXAnimation.m: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+MXAnimation.m 3 | // Yunlu 4 | // 5 | // Created by Michael on 2018/9/6. 6 | // Copyright © 2018年 DCloud. All rights reserved. 7 | // 8 | 9 | #import "MXAnimation.h" 10 | #import 11 | 12 | #pragma mark - -------------------------------- CAAnimation (StartEndCallbacks) ------------------------------- 13 | @implementation CAAnimation (StartEndCallbacks) 14 | 15 | #pragma mark - setter 16 | - (void)setAnimationDidStartBlock:(void (^)(CAAnimation *))animationDidStartBlock { 17 | [self setValue:animationDidStartBlock forKey:[NSString stringWithFormat:@"_%@", NSStringFromSelector(@selector(animationDidStartBlock))]]; 18 | } 19 | 20 | - (void)setAnimationDidStopBlock:(void (^)(CAAnimation *, BOOL))animationDidStopBlock { 21 | [self setValue:animationDidStopBlock forKey:[NSString stringWithFormat:@"_%@", NSStringFromSelector(@selector(animationDidStopBlock))]]; 22 | } 23 | 24 | #pragma mark - getter 25 | - (void (^)(CAAnimation *))animationDidStartBlock { 26 | return [self valueForKey:[NSString stringWithFormat:@"_%@", NSStringFromSelector(_cmd)]]; 27 | } 28 | 29 | - (void (^)(CAAnimation *, BOOL))animationDidStopBlock { 30 | return [self valueForKey:[NSString stringWithFormat:@"_%@", NSStringFromSelector(_cmd)]]; 31 | } 32 | 33 | @end 34 | 35 | #pragma mark - -------------------------------- MXAnimation ------------------------------- 36 | typedef NS_ENUM(NSUInteger, MXAnimationType) { 37 | MXAnimationTypeTranslateY, 38 | MXAnimationTypeAlpha, 39 | MXAnimationTypeScale, 40 | MXAnimationTypeRotationZ, 41 | }; 42 | 43 | @interface MXAnimation () 44 | 45 | @end 46 | 47 | @implementation MXAnimation 48 | 49 | #pragma mark - convenience animations 50 | + (CAKeyframeAnimation *)popOutScaleAnimationWithDuration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock { 51 | return [self scaleAnimatonWithValues:@[@0.01, @1.13, @0.9, @1.0] duration:duration updateBlock:updateBlock]; 52 | } 53 | 54 | + (CAKeyframeAnimation *)popInScaleAnimationWithDuration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock { 55 | return [self scaleAnimatonWithValues:@[@1.0, @0.01] duration:duration ?: 0.2f updateBlock:updateBlock]; 56 | } 57 | 58 | + (CAKeyframeAnimation *)upFromKeyWindowAnimationWithDuration:(NSTimeInterval)duration addToView:(UIView *)view updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock { 59 | CGFloat h = view.bounds.size.height, keyWindowH = [UIScreen mainScreen].bounds.size.height, safeH = 0; 60 | if (@available(iOS 11.0, *)) { 61 | safeH = [UIApplication sharedApplication].keyWindow.safeAreaInsets.bottom; 62 | } 63 | view.frame = CGRectMake(0, keyWindowH - h - safeH, view.bounds.size.width, h); 64 | return [self translateYAnimationWithValues:@[@(h + safeH), @0] duration:duration updateBlock:updateBlock]; 65 | } 66 | 67 | + (CAKeyframeAnimation *)downToKeyWindowAnimationWithDuration:(NSTimeInterval)duration addToView:(UIView *)view updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock { 68 | CGFloat h = view.bounds.size.height, keyWindowH = [UIScreen mainScreen].bounds.size.height, safeH = 0; 69 | if (@available(iOS 11.0, *)) { 70 | safeH = [UIApplication sharedApplication].keyWindow.safeAreaInsets.bottom; 71 | } 72 | view.frame = CGRectMake(0, keyWindowH - h - safeH, view.bounds.size.width, h); 73 | return [self translateYAnimationWithValues:@[@0, @(h + safeH)] duration:duration updateBlock:updateBlock]; 74 | } 75 | 76 | + (CAKeyframeAnimation *)showAlphaAnimationWithDuration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock { 77 | return [self alphaAnimatonWithValues:@[@(0.01f), @(1.0)] duration:duration updateBlock:updateBlock]; 78 | } 79 | 80 | + (CAKeyframeAnimation *)hideAlphaAnimationWithDuration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock { 81 | return [self alphaAnimatonWithValues:@[@(1.0), @(0.01f)] duration:duration updateBlock:updateBlock]; 82 | } 83 | 84 | + (CAKeyframeAnimation *)deleteAnimationWithDuration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock { 85 | return [self ratationZAnimatonWithValues:@[@0, @(-3/180.0f * M_PI), @0, @(3/180.0f * M_PI), @0] duration:duration updateBlock:^(CAKeyframeAnimation *animation) { 86 | animation.repeatCount = MAXFLOAT; 87 | }]; 88 | } 89 | 90 | #pragma mark - basic animations 91 | + (CAKeyframeAnimation *)translateYAnimationWithValues:(NSArray *)values duration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock { 92 | return [self animationWithType:MXAnimationTypeTranslateY values:values duration:duration updateBlock:updateBlock]; 93 | } 94 | 95 | + (CAKeyframeAnimation *)alphaAnimatonWithValues:(NSArray *)values duration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock { 96 | return [self animationWithType:MXAnimationTypeAlpha values:values duration:duration updateBlock:updateBlock]; 97 | } 98 | 99 | + (CAKeyframeAnimation *)scaleAnimatonWithValues:(NSArray *)values duration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock { 100 | return [self animationWithType:MXAnimationTypeScale values:values duration:duration updateBlock:updateBlock]; 101 | } 102 | 103 | + (CAKeyframeAnimation *)ratationZAnimatonWithValues:(NSArray *)values duration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock { 104 | return [self animationWithType:MXAnimationTypeRotationZ values:values duration:duration updateBlock:updateBlock]; 105 | } 106 | 107 | + (CAKeyframeAnimation *)animationWithType:(MXAnimationType)animationType values:(NSArray *)values duration:(NSTimeInterval)duration updateBlock:(void (^)(CAKeyframeAnimation *animation))updateBlock { 108 | 109 | NSString *keyPath = nil; 110 | switch (animationType) { 111 | case MXAnimationTypeTranslateY: 112 | keyPath = @"transform.translation.y"; 113 | break; 114 | case MXAnimationTypeAlpha: 115 | keyPath = @"opacity"; 116 | break; 117 | case MXAnimationTypeScale: 118 | keyPath = @"transform.scale"; 119 | break; 120 | case MXAnimationTypeRotationZ: 121 | keyPath = @"transform.rotation.z"; 122 | break; 123 | default: 124 | break; 125 | } 126 | if (!keyPath) return nil; 127 | CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:keyPath]; 128 | animation.values = values; 129 | animation.removedOnCompletion = NO; 130 | animation.fillMode = kCAFillModeForwards; 131 | animation.duration = duration ?: 0.35f; 132 | animation.delegate = (id)self; 133 | //animation.keyTimes = @[@0.0f, @0.3f, @0.6f, @1.0f]; 134 | !updateBlock ?:updateBlock(animation); 135 | return animation; 136 | } 137 | 138 | #pragma mark - CAAnimationDelegate 139 | + (void)animationDidStart:(CAAnimation *)anim { 140 | !anim.animationDidStartBlock ?: anim.animationDidStartBlock(anim); 141 | anim.animationDidStartBlock = nil; 142 | } 143 | 144 | + (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { 145 | !anim.animationDidStopBlock ?: anim.animationDidStopBlock(anim, flag); 146 | anim.animationDidStopBlock = nil; 147 | } 148 | 149 | @end 150 | -------------------------------------------------------------------------------- /MXPickerView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4CC88B742187E7A600A5C908 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC88B732187E7A600A5C908 /* AppDelegate.m */; }; 11 | 4CC88B772187E7A600A5C908 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC88B762187E7A600A5C908 /* ViewController.m */; }; 12 | 4CC88B7A2187E7A600A5C908 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4CC88B782187E7A600A5C908 /* Main.storyboard */; }; 13 | 4CC88B7C2187E7A700A5C908 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CC88B7B2187E7A700A5C908 /* Assets.xcassets */; }; 14 | 4CC88B7F2187E7A700A5C908 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4CC88B7D2187E7A700A5C908 /* LaunchScreen.storyboard */; }; 15 | 4CC88B822187E7A700A5C908 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC88B812187E7A700A5C908 /* main.m */; }; 16 | 4CC88B8E218858E000A5C908 /* MXAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC88B8A218858E000A5C908 /* MXAnimation.m */; }; 17 | 4CC88B8F218858E000A5C908 /* MXPickerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC88B8C218858E000A5C908 /* MXPickerView.m */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 4CC88B6F2187E7A600A5C908 /* MXPickerView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MXPickerView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 4CC88B722187E7A600A5C908 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 23 | 4CC88B732187E7A600A5C908 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 24 | 4CC88B752187E7A600A5C908 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 25 | 4CC88B762187E7A600A5C908 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 26 | 4CC88B792187E7A600A5C908 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 27 | 4CC88B7B2187E7A700A5C908 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 28 | 4CC88B7E2187E7A700A5C908 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 29 | 4CC88B802187E7A700A5C908 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 4CC88B812187E7A700A5C908 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 31 | 4CC88B89218858E000A5C908 /* MXAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXAnimation.h; sourceTree = ""; }; 32 | 4CC88B8A218858E000A5C908 /* MXAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXAnimation.m; sourceTree = ""; }; 33 | 4CC88B8C218858E000A5C908 /* MXPickerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXPickerView.m; sourceTree = ""; }; 34 | 4CC88B8D218858E000A5C908 /* MXPickerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXPickerView.h; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 4CC88B6C2187E7A600A5C908 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | 4CC88B662187E7A600A5C908 = { 49 | isa = PBXGroup; 50 | children = ( 51 | 4CC88B712187E7A600A5C908 /* MXPickerView */, 52 | 4CC88B702187E7A600A5C908 /* Products */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 4CC88B702187E7A600A5C908 /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 4CC88B6F2187E7A600A5C908 /* MXPickerView.app */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 4CC88B712187E7A600A5C908 /* MXPickerView */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 4CC88B88218858E000A5C908 /* MXAnimation */, 68 | 4CC88B8B218858E000A5C908 /* MXPickerView */, 69 | 4CC88B722187E7A600A5C908 /* AppDelegate.h */, 70 | 4CC88B732187E7A600A5C908 /* AppDelegate.m */, 71 | 4CC88B752187E7A600A5C908 /* ViewController.h */, 72 | 4CC88B762187E7A600A5C908 /* ViewController.m */, 73 | 4CC88B782187E7A600A5C908 /* Main.storyboard */, 74 | 4CC88B7B2187E7A700A5C908 /* Assets.xcassets */, 75 | 4CC88B7D2187E7A700A5C908 /* LaunchScreen.storyboard */, 76 | 4CC88B802187E7A700A5C908 /* Info.plist */, 77 | 4CC88B812187E7A700A5C908 /* main.m */, 78 | ); 79 | path = MXPickerView; 80 | sourceTree = ""; 81 | }; 82 | 4CC88B88218858E000A5C908 /* MXAnimation */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 4CC88B89218858E000A5C908 /* MXAnimation.h */, 86 | 4CC88B8A218858E000A5C908 /* MXAnimation.m */, 87 | ); 88 | path = MXAnimation; 89 | sourceTree = ""; 90 | }; 91 | 4CC88B8B218858E000A5C908 /* MXPickerView */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 4CC88B8D218858E000A5C908 /* MXPickerView.h */, 95 | 4CC88B8C218858E000A5C908 /* MXPickerView.m */, 96 | ); 97 | path = MXPickerView; 98 | sourceTree = ""; 99 | }; 100 | /* End PBXGroup section */ 101 | 102 | /* Begin PBXNativeTarget section */ 103 | 4CC88B6E2187E7A600A5C908 /* MXPickerView */ = { 104 | isa = PBXNativeTarget; 105 | buildConfigurationList = 4CC88B852187E7A700A5C908 /* Build configuration list for PBXNativeTarget "MXPickerView" */; 106 | buildPhases = ( 107 | 4CC88B6B2187E7A600A5C908 /* Sources */, 108 | 4CC88B6C2187E7A600A5C908 /* Frameworks */, 109 | 4CC88B6D2187E7A600A5C908 /* Resources */, 110 | ); 111 | buildRules = ( 112 | ); 113 | dependencies = ( 114 | ); 115 | name = MXPickerView; 116 | productName = MXPickerView; 117 | productReference = 4CC88B6F2187E7A600A5C908 /* MXPickerView.app */; 118 | productType = "com.apple.product-type.application"; 119 | }; 120 | /* End PBXNativeTarget section */ 121 | 122 | /* Begin PBXProject section */ 123 | 4CC88B672187E7A600A5C908 /* Project object */ = { 124 | isa = PBXProject; 125 | attributes = { 126 | LastUpgradeCheck = 0940; 127 | ORGANIZATIONNAME = Michael; 128 | TargetAttributes = { 129 | 4CC88B6E2187E7A600A5C908 = { 130 | CreatedOnToolsVersion = 9.4.1; 131 | }; 132 | }; 133 | }; 134 | buildConfigurationList = 4CC88B6A2187E7A600A5C908 /* Build configuration list for PBXProject "MXPickerView" */; 135 | compatibilityVersion = "Xcode 9.3"; 136 | developmentRegion = en; 137 | hasScannedForEncodings = 0; 138 | knownRegions = ( 139 | en, 140 | Base, 141 | ); 142 | mainGroup = 4CC88B662187E7A600A5C908; 143 | productRefGroup = 4CC88B702187E7A600A5C908 /* Products */; 144 | projectDirPath = ""; 145 | projectRoot = ""; 146 | targets = ( 147 | 4CC88B6E2187E7A600A5C908 /* MXPickerView */, 148 | ); 149 | }; 150 | /* End PBXProject section */ 151 | 152 | /* Begin PBXResourcesBuildPhase section */ 153 | 4CC88B6D2187E7A600A5C908 /* Resources */ = { 154 | isa = PBXResourcesBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | 4CC88B7F2187E7A700A5C908 /* LaunchScreen.storyboard in Resources */, 158 | 4CC88B7C2187E7A700A5C908 /* Assets.xcassets in Resources */, 159 | 4CC88B7A2187E7A600A5C908 /* Main.storyboard in Resources */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXResourcesBuildPhase section */ 164 | 165 | /* Begin PBXSourcesBuildPhase section */ 166 | 4CC88B6B2187E7A600A5C908 /* Sources */ = { 167 | isa = PBXSourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | 4CC88B772187E7A600A5C908 /* ViewController.m in Sources */, 171 | 4CC88B8E218858E000A5C908 /* MXAnimation.m in Sources */, 172 | 4CC88B822187E7A700A5C908 /* main.m in Sources */, 173 | 4CC88B8F218858E000A5C908 /* MXPickerView.m in Sources */, 174 | 4CC88B742187E7A600A5C908 /* AppDelegate.m in Sources */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXSourcesBuildPhase section */ 179 | 180 | /* Begin PBXVariantGroup section */ 181 | 4CC88B782187E7A600A5C908 /* Main.storyboard */ = { 182 | isa = PBXVariantGroup; 183 | children = ( 184 | 4CC88B792187E7A600A5C908 /* Base */, 185 | ); 186 | name = Main.storyboard; 187 | sourceTree = ""; 188 | }; 189 | 4CC88B7D2187E7A700A5C908 /* LaunchScreen.storyboard */ = { 190 | isa = PBXVariantGroup; 191 | children = ( 192 | 4CC88B7E2187E7A700A5C908 /* Base */, 193 | ); 194 | name = LaunchScreen.storyboard; 195 | sourceTree = ""; 196 | }; 197 | /* End PBXVariantGroup section */ 198 | 199 | /* Begin XCBuildConfiguration section */ 200 | 4CC88B832187E7A700A5C908 /* Debug */ = { 201 | isa = XCBuildConfiguration; 202 | buildSettings = { 203 | ALWAYS_SEARCH_USER_PATHS = NO; 204 | CLANG_ANALYZER_NONNULL = YES; 205 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 206 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 207 | CLANG_CXX_LIBRARY = "libc++"; 208 | CLANG_ENABLE_MODULES = YES; 209 | CLANG_ENABLE_OBJC_ARC = YES; 210 | CLANG_ENABLE_OBJC_WEAK = YES; 211 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 212 | CLANG_WARN_BOOL_CONVERSION = YES; 213 | CLANG_WARN_COMMA = YES; 214 | CLANG_WARN_CONSTANT_CONVERSION = YES; 215 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 216 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 217 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 218 | CLANG_WARN_EMPTY_BODY = YES; 219 | CLANG_WARN_ENUM_CONVERSION = YES; 220 | CLANG_WARN_INFINITE_RECURSION = YES; 221 | CLANG_WARN_INT_CONVERSION = YES; 222 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 223 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 224 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 225 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 226 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 227 | CLANG_WARN_STRICT_PROTOTYPES = YES; 228 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 229 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 230 | CLANG_WARN_UNREACHABLE_CODE = YES; 231 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 232 | CODE_SIGN_IDENTITY = "iPhone Developer"; 233 | COPY_PHASE_STRIP = NO; 234 | DEBUG_INFORMATION_FORMAT = dwarf; 235 | ENABLE_STRICT_OBJC_MSGSEND = YES; 236 | ENABLE_TESTABILITY = YES; 237 | GCC_C_LANGUAGE_STANDARD = gnu11; 238 | GCC_DYNAMIC_NO_PIC = NO; 239 | GCC_NO_COMMON_BLOCKS = YES; 240 | GCC_OPTIMIZATION_LEVEL = 0; 241 | GCC_PREPROCESSOR_DEFINITIONS = ( 242 | "DEBUG=1", 243 | "$(inherited)", 244 | ); 245 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 246 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 247 | GCC_WARN_UNDECLARED_SELECTOR = YES; 248 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 249 | GCC_WARN_UNUSED_FUNCTION = YES; 250 | GCC_WARN_UNUSED_VARIABLE = YES; 251 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 252 | MTL_ENABLE_DEBUG_INFO = YES; 253 | ONLY_ACTIVE_ARCH = YES; 254 | SDKROOT = iphoneos; 255 | }; 256 | name = Debug; 257 | }; 258 | 4CC88B842187E7A700A5C908 /* Release */ = { 259 | isa = XCBuildConfiguration; 260 | buildSettings = { 261 | ALWAYS_SEARCH_USER_PATHS = NO; 262 | CLANG_ANALYZER_NONNULL = YES; 263 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 264 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 265 | CLANG_CXX_LIBRARY = "libc++"; 266 | CLANG_ENABLE_MODULES = YES; 267 | CLANG_ENABLE_OBJC_ARC = YES; 268 | CLANG_ENABLE_OBJC_WEAK = YES; 269 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 270 | CLANG_WARN_BOOL_CONVERSION = YES; 271 | CLANG_WARN_COMMA = YES; 272 | CLANG_WARN_CONSTANT_CONVERSION = YES; 273 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 274 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 275 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 276 | CLANG_WARN_EMPTY_BODY = YES; 277 | CLANG_WARN_ENUM_CONVERSION = YES; 278 | CLANG_WARN_INFINITE_RECURSION = YES; 279 | CLANG_WARN_INT_CONVERSION = YES; 280 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 281 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 282 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 283 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 284 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 285 | CLANG_WARN_STRICT_PROTOTYPES = YES; 286 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 287 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 288 | CLANG_WARN_UNREACHABLE_CODE = YES; 289 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 290 | CODE_SIGN_IDENTITY = "iPhone Developer"; 291 | COPY_PHASE_STRIP = NO; 292 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 293 | ENABLE_NS_ASSERTIONS = NO; 294 | ENABLE_STRICT_OBJC_MSGSEND = YES; 295 | GCC_C_LANGUAGE_STANDARD = gnu11; 296 | GCC_NO_COMMON_BLOCKS = YES; 297 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 298 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 299 | GCC_WARN_UNDECLARED_SELECTOR = YES; 300 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 301 | GCC_WARN_UNUSED_FUNCTION = YES; 302 | GCC_WARN_UNUSED_VARIABLE = YES; 303 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 304 | MTL_ENABLE_DEBUG_INFO = NO; 305 | SDKROOT = iphoneos; 306 | VALIDATE_PRODUCT = YES; 307 | }; 308 | name = Release; 309 | }; 310 | 4CC88B862187E7A700A5C908 /* Debug */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 314 | CODE_SIGN_STYLE = Automatic; 315 | DEVELOPMENT_TEAM = YP4RBV4SRC; 316 | INFOPLIST_FILE = MXPickerView/Info.plist; 317 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 318 | LD_RUNPATH_SEARCH_PATHS = ( 319 | "$(inherited)", 320 | "@executable_path/Frameworks", 321 | ); 322 | PRODUCT_BUNDLE_IDENTIFIER = com.Yunlu.MXPickerView; 323 | PRODUCT_NAME = "$(TARGET_NAME)"; 324 | TARGETED_DEVICE_FAMILY = "1,2"; 325 | }; 326 | name = Debug; 327 | }; 328 | 4CC88B872187E7A700A5C908 /* Release */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 332 | CODE_SIGN_STYLE = Automatic; 333 | DEVELOPMENT_TEAM = YP4RBV4SRC; 334 | INFOPLIST_FILE = MXPickerView/Info.plist; 335 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 336 | LD_RUNPATH_SEARCH_PATHS = ( 337 | "$(inherited)", 338 | "@executable_path/Frameworks", 339 | ); 340 | PRODUCT_BUNDLE_IDENTIFIER = com.Yunlu.MXPickerView; 341 | PRODUCT_NAME = "$(TARGET_NAME)"; 342 | TARGETED_DEVICE_FAMILY = "1,2"; 343 | }; 344 | name = Release; 345 | }; 346 | /* End XCBuildConfiguration section */ 347 | 348 | /* Begin XCConfigurationList section */ 349 | 4CC88B6A2187E7A600A5C908 /* Build configuration list for PBXProject "MXPickerView" */ = { 350 | isa = XCConfigurationList; 351 | buildConfigurations = ( 352 | 4CC88B832187E7A700A5C908 /* Debug */, 353 | 4CC88B842187E7A700A5C908 /* Release */, 354 | ); 355 | defaultConfigurationIsVisible = 0; 356 | defaultConfigurationName = Release; 357 | }; 358 | 4CC88B852187E7A700A5C908 /* Build configuration list for PBXNativeTarget "MXPickerView" */ = { 359 | isa = XCConfigurationList; 360 | buildConfigurations = ( 361 | 4CC88B862187E7A700A5C908 /* Debug */, 362 | 4CC88B872187E7A700A5C908 /* Release */, 363 | ); 364 | defaultConfigurationIsVisible = 0; 365 | defaultConfigurationName = Release; 366 | }; 367 | /* End XCConfigurationList section */ 368 | }; 369 | rootObject = 4CC88B672187E7A600A5C908 /* Project object */; 370 | } 371 | -------------------------------------------------------------------------------- /MXPickerView/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // MXPickerView 4 | // 5 | // Created by Michael on 2018/10/30. 6 | // Copyright © 2018 Michael. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "MXPickerView.h" 11 | 12 | @interface ViewController () 13 | 14 | @property (nonatomic) NSMutableDictionary *selectedTitleOrCustomModelsDictM;// 记录每种mode的上一个`selectedTitleOrCustomModels` 15 | 16 | @end 17 | 18 | @implementation ViewController 19 | 20 | - (void)viewDidLoad { 21 | _selectedTitleOrCustomModelsDictM = @{}.mutableCopy; 22 | } 23 | 24 | #pragma mark - button actions 25 | - (IBAction)button:(UIButton *)sender { 26 | MXPickerViewMode mode = [self.view.subviews indexOfObject:sender]; 27 | switch (mode) { 28 | case MXPickerViewModeCustom: 29 | [self showPickerViewForModeCustom]; 30 | break; 31 | case MXPickerViewModeDD: 32 | case MXPickerViewModeDD_DD: 33 | case MXPickerViewModeMM: 34 | case MXPickerViewModeMM_MM: 35 | case MXPickerViewModeMM_DD: 36 | case MXPickerViewModeMMDD_MMDD: 37 | case MXPickerViewModeYYYY: 38 | case MXPickerViewModeYYYY_YYYY: 39 | case MXPickerViewModeYYYY_MM: 40 | case MXPickerViewModeYYYYMM_YYYYMM: 41 | case MXPickerViewModeYYYYMMDD_YYYYMMDD: 42 | [self showPickerViewForModeDate:mode]; 43 | break; 44 | case MXPickerViewModeTime: 45 | case MXPickerViewModeDate: 46 | case MXPickerViewModeDateAndTime: 47 | case MXPickerViewModeCountDownTimer: 48 | [self showPickerViewForModeTime:mode]; 49 | break; 50 | default: 51 | break; 52 | } 53 | } 54 | 55 | #pragma mark - private 56 | - (void)showPickerViewForModeCustom { 57 | [MXPickerView showWithSize:CGSizeMake(self.view.bounds.size.width, 200) pickerViewMode:MXPickerViewModeCustom updateBlock:^(MXPickerView *pickerView, MXPickerViewMode pickerViewMode, id uiDatePickerOrUIPickerView) { 58 | // 设置`contentlLabel`文字 59 | pickerView.modelsM = (id)@[@[@"0-0", @"0-1", @"0-2", @"0-3"], @[@"1-0", @"1-1", @"1-2", @"1-3"], @[@"2-0", @"2-1", @"2-2", @"2-3"]]; 60 | //pickerView.selectedTitleOrCustomModels = @[@"0-1", @"1-2", @"2-3"]; 61 | pickerView.selectedTitleOrCustomModels = (NSArray *)self.selectedTitleOrCustomModelsDictM[@(pickerViewMode)]; 62 | pickerView.configPickerViewTitleForRowForComponentBlock = ^NSString *(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component) { 63 | return [NSString stringWithFormat:@"%@", mxPickerView.modelsM[component][row]]; 64 | }; 65 | //pickerView.barBtnItemTitleTextAttributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:14], NSForegroundColorAttributeName: [UIColor redColor]}; 66 | pickerView.doneButtonDidClickBlock = ^(MXPickerViewMode pickerViewMode, CGFloat countDownDuration, NSDate *date, id selectedTitleOrCustomModels) { 67 | NSLog(@"selectedTitleOrCustomModels: %@", selectedTitleOrCustomModels); 68 | self.selectedTitleOrCustomModelsDictM[@(pickerViewMode)] = (NSArray *)selectedTitleOrCustomModels; 69 | }; 70 | /*// 设置`contentlLabel`属性字符串,打开注释 71 | pickerView.configPickerViewAttributedTitleForRowForComponentBlock = ^NSAttributedString *(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component) { 72 | return [[NSAttributedString alloc] initWithString:@"attributed title" attributes:@{NSForegroundColorAttributeName:[UIColor purpleColor]}]; 73 | };*/ 74 | /*// 需要为不同`component`返回不一样列高时,打开注释 75 | pickerView.configPickerViewWidthForComponentBlock = ^CGFloat(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component) { 76 | return (component == 0 || component == 3) ? 70 : (self.view.bounds.size.width - 2 * 80)/4; 77 | };*/ 78 | /*// 为每个`component`设置不同的行高, 相同的可以直接设置`pickerView.componentRowHeight = xx` 79 | pickerView.configPickerViewRowHeightForComponentBlock = ^CGFloat(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component) { 80 | return 100; 81 | };*/ 82 | //pickerView.componentRowHeight = 100; //默认每一列`component`行高都为40,当所有的`component`行高相等时,可统一设置, 相当于`tableView.rowHeight` 83 | //pickerView.toolBarPositionBottom = YES; //默认为`NO`,`返回`和`确定`按钮显示在顶部,当设为`YES`,`返回`和`确定`按钮显示在底部 84 | //pickerView.topTipBarTitles = @[@"开始日期", @"结束日期"]; //不为`nil`时,`toolBarPositionBottom`会自动设置为`YES`,底部显示`返回`和`确定`按钮,顶部显示`开始日期 - 结束日期` 85 | //pickerView.topTipBarH = 50; //`topTipBarTitles`一栏高度,默认是44 86 | //pickerView.toolBarH = 30; //`返回`和`确定`按钮一栏高度,默认是44 87 | //pickerView.backgroundColor = [UIColor lightGrayColor]; 88 | /*//自定义在`component`中的`contentView` 89 | pickerView.configPickerViewViewForRowForComponentBlock = ^UIView *(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component, UIView *reusingView) { 90 | return [UIButton buttonWithType:UIButtonTypeContactAdd]; 91 | };*/ 92 | // 想设置`contentLabel`的各个属性,实现`appearance block` 93 | pickerView.configPickerViewContentLabelAppearanceForRowForComponentBlock = ^(MXPickerView *mxPickerView, UIPickerView *pickerView, UILabel *contentLabel, NSInteger row, NSInteger component) { 94 | contentLabel.backgroundColor = [UIColor yellowColor]; 95 | contentLabel.textAlignment = NSTextAlignmentRight; 96 | }; 97 | /*pickerView.datePickerDidChangeBlock = ^(MXPickerViewMode pickerViewMode, CGFloat countDownDuration, NSDate *date, NSString *selectedTitle) { 98 | NSLog(@"pickerViewMode\n: %ld, countDownDuration\n: %f, date\n: %@, selectedTitle\n: %@", pickerViewMode, countDownDuration, date, selectedTitle); 99 | };*/ 100 | }]; 101 | } 102 | 103 | - (void)showPickerViewForModeDate:(MXPickerViewMode)mode { 104 | [MXPickerView showWithSize:CGSizeMake(self.view.bounds.size.width, 200) pickerViewMode:mode updateBlock:^(MXPickerView *pickerView, MXPickerViewMode pickerViewMode, id uiDatePickerOrUIPickerView) { 105 | switch (mode) { 106 | case MXPickerViewModeDD: 107 | case MXPickerViewModeDD_DD: { 108 | //创建从(今天 - 今天 + 10天),如(1号 - 10号)的列表,方式1和方式2结果一样 109 | //方式1,设置中间的时间间隔,超过当月的`最大天数`,代码内部会取余`deltaBetweenMaxAndMin%最大天数`,推荐方式一,方式二需要算日期,麻烦 110 | pickerView.minimumDate = [NSDate date]; 111 | pickerView.deltaBetweenMaxAndMin = 10; 112 | /* 113 | //方式2, 设置`最小日期`和`最大日期`,超过当月的`最大天数`,代码内部会取余`deltaBetweenMaxAndMin%最大天数` 114 | pickerView.minimumDate = [NSDate date]; 115 | pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 24 * 60 * 60 sinceDate:pickerView.minimumDate]; 116 | */ 117 | //方式3,什么都不写,全部使用默认值,1号 - 当月最大天数,`pickerView.deltaBetweenMaxAndMin = 当月最大天数`;//会根据当前模式设置默认值,`MXPickerViewModeDD_DD`会自动算当月最大几号,以及闰年 118 | // 119 | break; 120 | } 121 | case MXPickerViewModeMM: 122 | case MXPickerViewModeMM_MM: { 123 | //创建从(今月 - 今月 + 10月),如(1月 - 10月)的列表,方式1和方式2结果一样,推荐方式一,方式二需要算日期,麻烦 124 | //方式1,设置`最小日期`和`中间的时间间隔`,超过12,代码内部会取余`deltaBetweenMaxAndMin%12` 125 | pickerView.minimumDate = [NSDate date]; 126 | pickerView.deltaBetweenMaxAndMin = 10; 127 | /* 128 | //方式2, 设置`最小日期`和`最大日期`,超过12代码内部会取余`deltaBetweenMaxAndMin%12` 129 | pickerView.minimumDate = [NSDate date]; 130 | pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 60 * 60 sinceDate:pickerView.minimumDate]; 131 | */ 132 | //方式3,什么都不写,全部使用默认值 1月 - 12月,`pickerView.deltaBetweenMaxAndMin = 12`;//会根据当前模式设置默认值,`MXPickerViewModeMM_MM`则为12 133 | break; 134 | } 135 | case MXPickerViewModeMM_DD: // 1月1号 136 | case MXPickerViewModeMMDD_MMDD: { 137 | //创建从(今月1号 -(今月 + 10)31号)的列表,如(1月1号 - 10月31号),方式1和方式2结果一样,推荐方式一,方式二需要算日期,麻烦 138 | //方式1,设置中间的时间间隔,超过12,代码内部会取余`deltaBetweenMaxAndMin%12` 139 | pickerView.minimumDate = [NSDate date]; 140 | pickerView.deltaBetweenMaxAndMin = 10; 141 | ///* 142 | //方式2, 设置`最小日期`和`最大日期`,超过12,代码内部会取余`deltaBetweenMaxAndMin%12` 143 | //pickerView.minimumDate = [NSDate date]; 144 | //pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 30 * 24 * 60 * 60 sinceDate:pickerView.minimumDate]; 145 | //*/ 146 | //方式3,什么都不写,全部使用默认值,1月1号 - 12月31号,`pickerView.deltaBetweenMaxAndMin = 12`;//会根据当前模式为`MXPickerViewModeDD_DD`设置默认值12,会自动算当月最大几号,以及闰年 147 | break; 148 | } 149 | case MXPickerViewModeYYYY: 150 | case MXPickerViewModeYYYY_YYYY: { 151 | //创建从(今年 -(今年 + 10)的列表,如(2018年 - 2028年),方式1和方式2结果一样,推荐方式一,方式二需要算日期,麻烦 152 | //方式1,设置中间的时间间隔,超过12,代码内部会取余`deltaBetweenMaxAndMin%12` 153 | pickerView.minimumDate = [NSDate date]; 154 | pickerView.deltaBetweenMaxAndMin = 10; 155 | ///* 156 | //方式2, 设置最大的日期 157 | //pickerView.minimumDate = [NSDate date]; 158 | //pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 12 * 30 * 24 * 60 * 60 sinceDate:pickerView.minimumDate]; 159 | //*/ 160 | //方式3,什么都不写,全部使用默认值,1918年 - 今年,`pickerView.deltaBetweenMaxAndMin = 100`;//会根据当前模式为`MXPickerViewModeYYYY_YYYY`设置默认值100 161 | break; 162 | } 163 | case MXPickerViewModeYYYY_MM: // 2018年1月 164 | case MXPickerViewModeYYYYMM_YYYYMM: { 165 | //创建从(今年1月 -(今年 + 10)12月)的列表,如(2018年1月 - 2028年12月),方式1和方式2结果一样,推荐方式一,方式二需要算日期,麻烦 166 | //方式1,设置中间的时间间隔,超过12,代码内部会取余`deltaBetweenMaxAndMin%12` 167 | pickerView.minimumDate = [NSDate date]; 168 | pickerView.deltaBetweenMaxAndMin = 10; 169 | ///* 170 | //方式2, 设置最大的日期 171 | //pickerView.minimumDate = [NSDate date]; 172 | //pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 12 * 30 * 24 * 60 * 60 sinceDate:pickerView.minimumDate]; 173 | //*/ 174 | //方式3,什么都不写,全部使用默认值,1918年1月 - 今年12月,`pickerView.deltaBetweenMaxAndMin = 100`;//会根据当前模式为`MXPickerViewModeYYYY_YYYY`设置默认值100 175 | break; 176 | } 177 | case MXPickerViewModeYYYYMMDD_YYYYMMDD: { 178 | //pickerView.componentWidth = 70;//6列需要在block中单独设置每一列的宽度,要不然2018显示20.. 179 | //创建从(今年1月1号 -(今年 + 10)12月31号)的列表,如(2018年1月1号 - 2028年12月31号),方式1和方式2结果一样,推荐方式一,方式二需要算日期,麻烦 180 | //方式1,设置中间的时间间隔,超过12,代码内部会取余`deltaBetweenMaxAndMin%12` 181 | pickerView.minimumDate = [NSDate date]; 182 | pickerView.deltaBetweenMaxAndMin = 10; 183 | ///* 184 | //方式2, 设置最大的日期 185 | //pickerView.minimumDate = [NSDate date]; 186 | //pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 12 * 30 * 24 * 60 * 60 sinceDate:pickerView.minimumDate]; 187 | //*/ 188 | //方式3,什么都不写,全部使用默认值,1918年1月1号 - 今年12月31号,`pickerView.deltaBetweenMaxAndMin = 100`;//会根据当前模式为`MXPickerViewModeYYYY_YYYY`设置默认值100 189 | break; 190 | } 191 | default: 192 | break; 193 | } 194 | //pickerView.showsDescending = YES;//默认升序,结果为12, 1, 2, 3... 11, 12,当为降序,12, 11, 10,...4, 3, 2, 1, 12 195 | 196 | //pickerView.componentRowHeight = 100; //默认每一列`component`行高都为40,当所有的`component`行高相等时,可统一设置, 相当于`tableView.rowHeight` 197 | //需要为不同`component`返回不一样行高时,打开注释 198 | /*pickerView.configPickerViewRowHeightForComponentBlock = ^CGFloat(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component) { 199 | return 100; 200 | };*/ 201 | 202 | //pickerView.componentWidth = 100; //默认每一列`component`列宽都为`pickerView.bounds.size.width/numberOfComponents`,当所有的`component`行高相等时,可统一设置 203 | //需要为不同`component`返回不一样列宽时,打开注释 204 | if (mode == MXPickerViewModeYYYYMMDD_YYYYMMDD) { 205 | pickerView.configPickerViewWidthForComponentBlock = ^CGFloat(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component) { 206 | return (component == 0 || component == 3) ? 60 : (mxPickerView.bounds.size.width - 2 * 60 - 15)/4; 207 | }; 208 | } 209 | 210 | //pickerView.toolBarPositionBottom = YES; //默认为`NO`,`返回`和`确定`按钮显示在顶部,当设为`YES`,`返回`和`确定`按钮显示在底部 211 | //pickerView.topTipBarTitles = @[@"开始日期", @"结束日期"]; //不为`nil`时,`toolBarPositionBottom`会自动设置为`YES`,底部显示`返回`和`确定`按钮,顶部显示`开始日期 - 结束日期` 212 | //pickerView.topTipBarH = 50; //`topTipBarTitles`一栏高度,默认是44 213 | //pickerView.toolBarH = 30; //`返回`和`确定`按钮一栏高度,默认是44 214 | //pickerView.backgroundColor = [UIColor lightGrayColor]; 215 | /* 216 | // 想设置`contentLabel`的各个属性,实现`appearance block` 217 | pickerView.configPickerViewContentLabelAppearanceForRowForComponentBlock = ^(MXPickerView *mxPickerView, UIPickerView *pickerView, UILabel *contentLabel, NSInteger row, NSInteger component) { 218 | contentLabel.backgroundColor = [UIColor yellowColor]; 219 | contentLabel.textAlignment = NSTextAlignmentRight; 220 | };*/ 221 | pickerView.yearLocale = @"年"; 222 | pickerView.monthLocale = @"月"; 223 | pickerView.dayLocale = @"号"; 224 | pickerView.selectedTitleOrCustomModels = (NSString *)self.selectedTitleOrCustomModelsDictM[@(mode)];; 225 | pickerView.doneButtonDidClickBlock = ^(MXPickerViewMode pickerViewMode, CGFloat countDownDuration, NSDate *date, id selectedTitleOrCustomModels) { 226 | NSLog(@"selectedTitleOrCustomModels: %@", selectedTitleOrCustomModels); 227 | self.selectedTitleOrCustomModelsDictM[@(pickerViewMode)] = (NSString *)selectedTitleOrCustomModels; 228 | }; 229 | }]; 230 | } 231 | 232 | 233 | - (void)showPickerViewForModeTime:(MXPickerViewMode)mode { 234 | [MXPickerView showWithSize:CGSizeMake(self.view.bounds.size.width, 200) pickerViewMode:mode updateBlock:^(MXPickerView *pickerView, MXPickerViewMode pickerViewMode, id uiDatePickerOrUIPickerView) { 235 | UIDatePicker *datePicker = (UIDatePicker *)uiDatePickerOrUIPickerView; 236 | datePicker.minuteInterval = 1; 237 | switch (mode) { 238 | case MXPickerViewModeTime: { 239 | //前1小时 - 后1小时 240 | datePicker.date = [NSDate date]; 241 | datePicker.minimumDate = [NSDate dateWithTimeInterval:-1 * 60 * 60 sinceDate:datePicker.date]; 242 | datePicker.maximumDate = [NSDate dateWithTimeInterval:1 * 60 * 60 sinceDate:datePicker.date]; 243 | break; 244 | } 245 | case MXPickerViewModeDate: 246 | case MXPickerViewModeDateAndTime: { 247 | //前天 - 后天 248 | datePicker.date = [NSDate date]; 249 | datePicker.minimumDate = [NSDate dateWithTimeInterval:-24 * 60 * 60 sinceDate:datePicker.date]; 250 | datePicker.maximumDate = [NSDate dateWithTimeInterval:24 * 60 * 60 sinceDate:datePicker.date]; 251 | break; 252 | } 253 | case MXPickerViewModeCountDownTimer: { 254 | datePicker.countDownDuration = 60 * 10;//选中为10分钟 255 | break; 256 | } 257 | default: 258 | break; 259 | } 260 | //pickerView.toolBarPositionBottom = YES; //默认为`NO`,`返回`和`确定`按钮显示在顶部,当设为`YES`,`返回`和`确定`按钮显示在底部 261 | //pickerView.topTipBarTitles = @[@"开始日期", @"结束日期"]; //不为`nil`时,`toolBarPositionBottom`会自动设置为`YES`,底部显示`返回`和`确定`按钮,顶部显示`开始日期 - 结束日期` 262 | //pickerView.topTipBarH = 50; //`topTipBarTitles`一栏高度,默认是44 263 | //pickerView.toolBarH = 30; //`返回`和`确定`按钮一栏高度,默认是44 264 | //pickerView.backgroundColor = [UIColor lightGrayColor]; 265 | pickerView.doneButtonDidClickBlock = ^(MXPickerViewMode pickerViewMode, CGFloat countDownDuration, NSDate *date, id selectedTitleOrCustomModels) { 266 | NSLog(@"date: %@", date); 267 | }; 268 | }]; 269 | } 270 | 271 | @end 272 | -------------------------------------------------------------------------------- /MXPickerView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 83 | 91 | 102 | 113 | 124 | 135 | 146 | 157 | 168 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # MXPickerView 6 | 7 | **MXPickerView** is an encapsulation for **UIPickerView** and **UIDatePicker** 8 | 9 | ## ScreenShot 10 | 11 | MXPickerViewModeCustom | MXPickerViewModeDD | MXPickerViewModeDD_DD | MXPickerViewModeMM 12 | :---:|:---:| :---:| :---:| 13 | | | | 14 | **MXPickerViewModeMM_MM** | **MXPickerViewModeMM_DD** | **MXPickerViewModeMMDD_MMDD** | **MXPickerViewModeYYYY** 15 | | | | 16 | **MXPickerViewModeYYYY_YYYY** | **MXPickerViewModeYYYY_MM** | **MXPickerViewModeYYYYMM_YYYYMM** | **MXPickerViewModeYYYYMMDD_YYYYMMDD** 17 | | | | 18 | **MXPickerViewModeTime** | **MXPickerViewModeDate** | **MXPickerViewModeDateAndTime** | **MXPickerViewModeCountDownTimer** 19 | | | | 20 | 21 | 22 | 23 | 24 | 25 | ## Attributes 26 | 27 | ScreenshotsInDemoxxxxxxxxx | Attribute Name | Example 28 | :---: | :---: | :---: | 29 | | NSDictionary *barBtnItemTitleTextAttributes; | pickerView.barBtnItemTitleTextAttributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:14], NSForegroundColorAttributeName: [UIColor redColor]} 30 | | NSString *yearLocale, *monthLocale, *dayLocale; | pickerView.yearLocale = @"年";
pickerView.monthLocale = @"月";
pickerView.dayLocale = @"号";
31 | |
BOOL toolBarPositionBottom;

//默认为`NO`,`返回`和`确定`按钮显示在顶部,
当设为`YES`,`返回`和`确定`按钮显示在底部

| pickerView.toolBarPositionBottom = YES; 32 | |
NSArray *topTipBarTitles;

//不为`nil`时, `toolBarPositionBottom`
会自动设置为`YES`,底部显示`返回`和`确定`按钮,
顶部显示`topTipBarTitles`, 如`开始日期 - 结束日期`

| pickerView.topTipBarTitles = @[@"开始日期", @"结束日期"]; 33 | |
CGFloat topTipBarH, toolBarH;

pickerView.topTipBarH = 50;
pickerView.toolBarH = 30;
// 底部显示`返回`和`确定`按钮,顶部显示`开始日期 - 结束日期`
`topTipBarTitles`一栏高度,默认是44;
`返回`和`确定`按钮一栏高度,默认是44

| pickerView.topTipBarTitles = @[@"开始日期", @"结束日期"]; 34 | |
BOOL showsDescending;

// 默认升序,结果为12, 1, 2, 3... 11, 12,
当为降序,结果为12, 11, 10, ...4, 3, 2, 1, 12, 仅支持`mode`是日期类的,如`MXPickerViewModeDD`...

| pickerView.showsDescending = YES; 35 | |
id selectedTitleOrCustomModels;

//如果值不为空,则`pickerView`会把当前值选中,
demo中是把上一个值记录为`selectedTitleOrCustomModels`,
当模式为`MXPickerViewModeCustom`, `selectedTitleOrCustomModels` 应该为`customModels`, 是一个 `NSArray`数组类型, 如 `@[@"0-1", @"1-2", @"2-3"]`
当模式为日期一类的,如`MXPickerViewModeDD`..., `selectedTitleOrCustomModels`应该为`selectedTitle`, 是一个用空格拼接的`NSString`数组类型, 格式类似于`2018 12 12`
当模式为`UIDatePicker`一类的, 如`MXPickerViewModeDateAndTime`, `selectedTitleOrCustomModels` 不应该传, 默认值总是nil!

| //当模式为`MXPickerViewModeCustom`
pickerView.selectedTitleOrCustomModels = @[@"0-1", @"1-2", @"2-3"];

//当模式为日期一类的,如`MXPickerViewModeDD`...,
pickerView.selectedTitleOrCustomModels = @"12月 1号" 36 | | NSDate *minimumDate;
NSDate *maximumDate;
//仅支持`mode`是日期类的,如`MXPickerViewModeDD`...

|
//创建从(今天 - 今天 + 10天),如(1号 - 10号)的列表

//方式1,设置`中间的时间间隔`,超过当月的`最大天数`,
代码内部会取余`deltaBetweenMaxAndMin%最大天数`,
推荐方式一
pickerView.minimumDate = [NSDate date];
pickerView.deltaBetweenMaxAndMin = 10;

//方式二, 设置`最小日期`和`最大日期`,超过
当月的`最大天数`,代码内部会取余
`deltaBetweenMaxAndMin%最大天数`
pickerView.minimumDate = [NSDate date];
pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 24 * 60 * 60 sinceDate:pickerView.minimumDate];

37 | |
CGFloat componentWidth;
//设置每个`component`的宽度,等宽,如果不设置,
默认为`pickerView.bounds.size.width/numberOfComponents`

| pickerView.componentWidth = 100;
//每一列`component`列宽都为`100` 38 | |
CGFloat componentRowHeight;
//设置每个`component`的行高,如果不设置,默认为`40`

| pickerView.componentRowHeight = 100; 39 | |
`CGFloat (^configPickerViewWidthForComponentBlock)(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component)`
//为每个`component`设置不同的宽度,相同的可以直接设置`pickerView.componentWidth = xx`

| ```pickerView.configPickerViewWidthForComponentBlock = ^CGFloat(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component) { return (component == 0 || component == 3) ? 80 : (mxPickerView.bounds.size.width - 2 * 80)/4;};``` 40 | |
`CGFloat(^configPickerViewRowHeightForComponentBlock)(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component)`
//为每个`component`设置相同的行高, 等价于`pickerView.componentRowHeight = xx`

| ```pickerView.configPickerViewRowHeightForComponentBlock = ^CGFloat(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component) {return 100;};``` 41 | |
`NSString * (^configPickerViewTitleForRowForComponentBlock)(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component);`
//设置每一行的`title`

| ```pickerView.configPickerViewTitleForRowForComponentBlock = ^NSString *(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component) {return @"row content";};``` 42 | |
`NSAttributedString * (^configPickerViewAttributedTitleForRowForComponentBlock)(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component)`
//设置每一行的`attributedTitle`

| ```pickerView.configPickerViewAttributedTitleForRowForComponentBlock = ^NSAttributedString *(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component) {return [[NSAttributedString alloc] initWithString:@"attributed title" attributes:@{NSForegroundColorAttributeName:[UIColor purpleColor]}];};``` 43 | |
`void (^configPickerViewContentLabelAppearanceForRowForComponentBlock)(MXPickerView *mxPickerView, UIPickerView *pickerView, UILabel *contentLabel, NSInteger row, NSInteger component);`
//设置 content label 的样式,如字体,文字对齐方式,背景颜色等...

| ```pickerView.configPickerViewContentLabelAppearanceForRowForComponentBlock = ^(MXPickerView *mxPickerView, UIPickerView *pickerView, UILabel *contentLabel, NSInteger row, NSInteger component){ contentLabel.backgroundColor = [UIColor yellowColor];//黄色 contentLabel.textAlignment = NSTextAlignmentRight;};//右对齐``` 44 | |
` UIView * (^configPickerViewViewForRowForComponentBlock)(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component, UIView *reusingView);`
//自定义在`component`中的`contentView`

| ```pickerView.configPickerViewViewForRowForComponentBlock = ^UIView *(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component, UIView *reusingView) {return [UIButton buttonWithType:UIButtonTypeContactAdd];//返回UIButtonTypeContactAdd};``` 45 | 46 | 47 | 48 | 49 | ## How to use 50 | 51 | ### MXPickerViewModeCustom 52 | 53 | ```objective-c 54 | [MXPickerView showWithSize:CGSizeMake(self.view.bounds.size.width, 200) pickerViewMode:MXPickerViewModeCustom updateBlock:^(MXPickerView *pickerView, MXPickerViewMode pickerViewMode, id uiDatePickerOrUIPickerView) { 55 | // 设置`contentlLabel`文字 56 | pickerView.modelsM = (id)@[@[@"0-0", @"0-1", @"0-2", @"0-3"], @[@"1-0", @"1-1", @"1-2", @"1-3"], @[@"2-0", @"2-1", @"2-2", @"2-3"]]; 57 | //pickerView.selectedTitleOrCustomModels = @[@"0-1", @"1-2", @"2-3"]; 58 | pickerView.selectedTitleOrCustomModels = (NSArray *)self.selectedTitleOrCustomModelsDictM[@(pickerViewMode)]; 59 | pickerView.configPickerViewTitleForRowForComponentBlock = ^NSString *(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component) { 60 | return [NSString stringWithFormat:@"%@", mxPickerView.modelsM[component][row]]; 61 | }; 62 | //pickerView.barBtnItemTitleTextAttributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:14], NSForegroundColorAttributeName: [UIColor redColor]}; 63 | pickerView.doneButtonDidClickBlock = ^(MXPickerViewMode pickerViewMode, CGFloat countDownDuration, NSDate *date, id selectedTitleOrCustomModels) { 64 | NSLog(@"selectedTitleOrCustomModels: %@", selectedTitleOrCustomModels); 65 | self.selectedTitleOrCustomModelsDictM[@(pickerViewMode)] = (NSArray *)selectedTitleOrCustomModels; 66 | }; 67 | /*// 设置`contentlLabel`属性字符串,打开注释 68 | pickerView.configPickerViewAttributedTitleForRowForComponentBlock = ^NSAttributedString *(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component) { 69 | return [[NSAttributedString alloc] initWithString:@"attributed title" attributes:@{NSForegroundColorAttributeName:[UIColor purpleColor]}]; 70 | };*/ 71 | /*// 需要为不同`component`返回不一样列高时,打开注释 72 | pickerView.configPickerViewWidthForComponentBlock = ^CGFloat(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component) { 73 | return (component == 0 || component == 3) ? 70 : (self.view.bounds.size.width - 2 * 80)/4; 74 | };*/ 75 | /*// 为每个`component`设置不同的行高, 相同的可以直接设置`pickerView.componentRowHeight = xx` 76 | pickerView.configPickerViewRowHeightForComponentBlock = ^CGFloat(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component) { 77 | return 100; 78 | };*/ 79 | //pickerView.componentRowHeight = 100; //默认每一列`component`行高都为40,当所有的`component`行高相等时,可统一设置, 相当于`tableView.rowHeight` 80 | //pickerView.toolBarPositionBottom = YES; //默认为`NO`,`返回`和`确定`按钮显示在顶部,当设为`YES`,`返回`和`确定`按钮显示在底部 81 | //pickerView.topTipBarTitles = @[@"开始日期", @"结束日期"]; //不为`nil`时,`toolBarPositionBottom`会自动设置为`YES`,底部显示`返回`和`确定`按钮,顶部显示`开始日期 - 结束日期` 82 | //pickerView.topTipBarH = 50; //`topTipBarTitles`一栏高度,默认是44 83 | //pickerView.toolBarH = 30; //`返回`和`确定`按钮一栏高度,默认是44 84 | //pickerView.backgroundColor = [UIColor lightGrayColor]; 85 | /*//自定义在`component`中的`contentView` 86 | pickerView.configPickerViewViewForRowForComponentBlock = ^UIView *(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger row, NSInteger component, UIView *reusingView) { 87 | return [UIButton buttonWithType:UIButtonTypeContactAdd]; 88 | };*/ 89 | // 想设置`contentLabel`的各个属性,实现`appearance block` 90 | pickerView.configPickerViewContentLabelAppearanceForRowForComponentBlock = ^(MXPickerView *mxPickerView, UIPickerView *pickerView, UILabel *contentLabel, NSInteger row, NSInteger component) { 91 | contentLabel.backgroundColor = [UIColor yellowColor]; 92 | contentLabel.textAlignment = NSTextAlignmentRight; 93 | }; 94 | /*pickerView.datePickerDidChangeBlock = ^(MXPickerViewMode pickerViewMode, CGFloat countDownDuration, NSDate *date, NSString *selectedTitle) { 95 | NSLog(@"pickerViewMode\n: %ld, countDownDuration\n: %f, date\n: %@, selectedTitle\n: %@", pickerViewMode, countDownDuration, date, selectedTitle); 96 | };*/ 97 | }]; 98 | ``` 99 | 100 | ### Other date Mode 101 | 102 | **MXPickerViewModeDD**, 103 | **MXPickerViewModeDD_DD** 104 | **MXPickerViewModeMM** 105 | **MXPickerViewModeMM_MM** 106 | **MXPickerViewModeMM_DD** 107 | **MXPickerViewModeMMDD_MMDD** 108 | **MXPickerViewModeYYYY** 109 | **MXPickerViewModeYYYY_YYYY** 110 | **MXPickerViewModeYYYY_MM** 111 | **MXPickerViewModeYYYYMM_YYYYMM** 112 | **MXPickerViewModeYYYYMMDD_YYYYMMDD** 113 | 114 | ``` objc 115 | [MXPickerView showWithSize:CGSizeMake(self.view.bounds.size.width, 200) pickerViewMode:mode updateBlock:^(MXPickerView *pickerView, MXPickerViewMode pickerViewMode, id uiDatePickerOrUIPickerView) { 116 | switch (mode) { 117 | case MXPickerViewModeDD: 118 | case MXPickerViewModeDD_DD: { 119 | //创建从(今天 - 今天 + 10天),如(1号 - 10号)的列表,方式1和方式2结果一样 120 | //方式1,设置中间的时间间隔,超过当月的`最大天数`,代码内部会取余`deltaBetweenMaxAndMin%最大天数`,推荐方式一,方式二需要算日期,麻烦 121 | pickerView.minimumDate = [NSDate date]; 122 | pickerView.deltaBetweenMaxAndMin = 10; 123 | /* 124 | //方式2, 设置`最小日期`和`最大日期`,超过当月的`最大天数`,代码内部会取余`deltaBetweenMaxAndMin%最大天数` 125 | pickerView.minimumDate = [NSDate date]; 126 | pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 24 * 60 * 60 sinceDate:pickerView.minimumDate]; 127 | */ 128 | //方式3,什么都不写,全部使用默认值,1号 - 当月最大天数,`pickerView.deltaBetweenMaxAndMin = 当月最大天数`;//会根据当前模式设置默认值,`MXPickerViewModeDD_DD`会自动算当月最大几号,以及闰年 129 | // 130 | break; 131 | } 132 | case MXPickerViewModeMM: 133 | case MXPickerViewModeMM_MM: { 134 | //创建从(今月 - 今月 + 10月),如(1月 - 10月)的列表,方式1和方式2结果一样,推荐方式一,方式二需要算日期,麻烦 135 | //方式1,设置`最小日期`和`中间的时间间隔`,超过12,代码内部会取余`deltaBetweenMaxAndMin%12` 136 | pickerView.minimumDate = [NSDate date]; 137 | pickerView.deltaBetweenMaxAndMin = 10; 138 | /* 139 | //方式2, 设置`最小日期`和`最大日期`,超过12代码内部会取余`deltaBetweenMaxAndMin%12` 140 | pickerView.minimumDate = [NSDate date]; 141 | pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 60 * 60 sinceDate:pickerView.minimumDate]; 142 | */ 143 | //方式3,什么都不写,全部使用默认值 1月 - 12月,`pickerView.deltaBetweenMaxAndMin = 12`;//会根据当前模式设置默认值,`MXPickerViewModeMM_MM`则为12 144 | break; 145 | } 146 | case MXPickerViewModeMM_DD: // 1月1号 147 | case MXPickerViewModeMMDD_MMDD: { 148 | //创建从(今月1号 -(今月 + 10)31号)的列表,如(1月1号 - 10月31号),方式1和方式2结果一样,推荐方式一,方式二需要算日期,麻烦 149 | //方式1,设置中间的时间间隔,超过12,代码内部会取余`deltaBetweenMaxAndMin%12` 150 | pickerView.minimumDate = [NSDate date]; 151 | pickerView.deltaBetweenMaxAndMin = 10; 152 | ///* 153 | //方式2, 设置`最小日期`和`最大日期`,超过12,代码内部会取余`deltaBetweenMaxAndMin%12` 154 | //pickerView.minimumDate = [NSDate date]; 155 | //pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 30 * 24 * 60 * 60 sinceDate:pickerView.minimumDate]; 156 | //*/ 157 | //方式3,什么都不写,全部使用默认值,1月1号 - 12月31号,`pickerView.deltaBetweenMaxAndMin = 12`;//会根据当前模式为`MXPickerViewModeDD_DD`设置默认值12,会自动算当月最大几号,以及闰年 158 | break; 159 | } 160 | case MXPickerViewModeYYYY: 161 | case MXPickerViewModeYYYY_YYYY: { 162 | //创建从(今年 -(今年 + 10)的列表,如(2018年 - 2028年),方式1和方式2结果一样,推荐方式一,方式二需要算日期,麻烦 163 | //方式1,设置中间的时间间隔,超过12,代码内部会取余`deltaBetweenMaxAndMin%12` 164 | pickerView.minimumDate = [NSDate date]; 165 | pickerView.deltaBetweenMaxAndMin = 10; 166 | ///* 167 | //方式2, 设置最大的日期 168 | //pickerView.minimumDate = [NSDate date]; 169 | //pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 12 * 30 * 24 * 60 * 60 sinceDate:pickerView.minimumDate]; 170 | //*/ 171 | //方式3,什么都不写,全部使用默认值,1918年 - 今年,`pickerView.deltaBetweenMaxAndMin = 100`;//会根据当前模式为`MXPickerViewModeYYYY_YYYY`设置默认值100 172 | break; 173 | } 174 | case MXPickerViewModeYYYY_MM: // 2018年1月 175 | case MXPickerViewModeYYYYMM_YYYYMM: { 176 | //创建从(今年1月 -(今年 + 10)12月)的列表,如(2018年1月 - 2028年12月),方式1和方式2结果一样,推荐方式一,方式二需要算日期,麻烦 177 | //方式1,设置中间的时间间隔,超过12,代码内部会取余`deltaBetweenMaxAndMin%12` 178 | pickerView.minimumDate = [NSDate date]; 179 | pickerView.deltaBetweenMaxAndMin = 10; 180 | ///* 181 | //方式2, 设置最大的日期 182 | //pickerView.minimumDate = [NSDate date]; 183 | //pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 12 * 30 * 24 * 60 * 60 sinceDate:pickerView.minimumDate]; 184 | //*/ 185 | //方式3,什么都不写,全部使用默认值,1918年1月 - 今年12月,`pickerView.deltaBetweenMaxAndMin = 100`;//会根据当前模式为`MXPickerViewModeYYYY_YYYY`设置默认值100 186 | break; 187 | } 188 | case MXPickerViewModeYYYYMMDD_YYYYMMDD: { 189 | //pickerView.componentWidth = 70;//6列需要在block中单独设置每一列的宽度,要不然2018显示20.. 190 | //创建从(今年1月1号 -(今年 + 10)12月31号)的列表,如(2018年1月1号 - 2028年12月31号),方式1和方式2结果一样,推荐方式一,方式二需要算日期,麻烦 191 | //方式1,设置中间的时间间隔,超过12,代码内部会取余`deltaBetweenMaxAndMin%12` 192 | pickerView.minimumDate = [NSDate date]; 193 | pickerView.deltaBetweenMaxAndMin = 10; 194 | ///* 195 | //方式2, 设置最大的日期 196 | //pickerView.minimumDate = [NSDate date]; 197 | //pickerView.maximumDate = [NSDate dateWithTimeInterval:10 * 12 * 30 * 24 * 60 * 60 sinceDate:pickerView.minimumDate]; 198 | //*/ 199 | //方式3,什么都不写,全部使用默认值,1918年1月1号 - 今年12月31号,`pickerView.deltaBetweenMaxAndMin = 100`;//会根据当前模式为`MXPickerViewModeYYYY_YYYY`设置默认值100 200 | break; 201 | } 202 | default: 203 | break; 204 | } 205 | //pickerView.showsDescending = YES;//默认升序,结果为12, 1, 2, 3... 11, 12,当为降序,12, 11, 10,...4, 3, 2, 1, 12 206 | 207 | //pickerView.componentRowHeight = 100; //默认每一列`component`行高都为40,当所有的`component`行高相等时,可统一设置, 相当于`tableView.rowHeight` 208 | //需要为不同`component`返回不一样行高时,打开注释 209 | /*pickerView.configPickerViewRowHeightForComponentBlock = ^CGFloat(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component) { 210 | return 100; 211 | };*/ 212 | 213 | //pickerView.componentWidth = 100; //默认每一列`component`列宽都为`pickerView.bounds.size.width/numberOfComponents`,当所有的`component`行高相等时,可统一设置 214 | //需要为不同`component`返回不一样列宽时,打开注释 215 | if (mode == MXPickerViewModeYYYYMMDD_YYYYMMDD) { 216 | pickerView.configPickerViewWidthForComponentBlock = ^CGFloat(MXPickerView *mxPickerView, UIPickerView *pickerView, NSInteger component) { 217 | return (component == 0 || component == 3) ? 60 : (mxPickerView.bounds.size.width - 2 * 60 - 15)/4; 218 | }; 219 | } 220 | 221 | //pickerView.toolBarPositionBottom = YES; //默认为`NO`,`返回`和`确定`按钮显示在顶部,当设为`YES`,`返回`和`确定`按钮显示在底部 222 | //pickerView.topTipBarTitles = @[@"开始日期", @"结束日期"]; //不为`nil`时,`toolBarPositionBottom`会自动设置为`YES`,底部显示`返回`和`确定`按钮,顶部显示`开始日期 - 结束日期` 223 | //pickerView.topTipBarH = 50; //`topTipBarTitles`一栏高度,默认是44 224 | //pickerView.toolBarH = 30; //`返回`和`确定`按钮一栏高度,默认是44 225 | //pickerView.backgroundColor = [UIColor lightGrayColor]; 226 | /* 227 | // 想设置`contentLabel`的各个属性,实现`appearance block` 228 | pickerView.configPickerViewContentLabelAppearanceForRowForComponentBlock = ^(MXPickerView *mxPickerView, UIPickerView *pickerView, UILabel *contentLabel, NSInteger row, NSInteger component) { 229 | contentLabel.backgroundColor = [UIColor yellowColor]; 230 | contentLabel.textAlignment = NSTextAlignmentRight; 231 | };*/ 232 | pickerView.yearLocale = @"年"; 233 | pickerView.monthLocale = @"月"; 234 | pickerView.dayLocale = @"号"; 235 | pickerView.selectedTitleOrCustomModels = (NSString *)self.selectedTitleOrCustomModelsDictM[@(mode)];; 236 | pickerView.doneButtonDidClickBlock = ^(MXPickerViewMode pickerViewMode, CGFloat countDownDuration, NSDate *date, id selectedTitleOrCustomModels) { 237 | NSLog(@"selectedTitleOrCustomModels: %@", selectedTitleOrCustomModels); 238 | self.selectedTitleOrCustomModelsDictM[@(pickerViewMode)] = (NSString *)selectedTitleOrCustomModels; 239 | }; 240 | }]; 241 | ``` 242 | 243 | ### Date and time 244 | 245 | **MXPickerViewModeTime** 246 | **MXPickerViewModeDate** 247 | **MXPickerViewModeDateAndTime** 248 | **MXPickerViewModeCountDownTimer** 249 | 250 | ``` objective-c 251 | [MXPickerView showWithSize:CGSizeMake(self.view.bounds.size.width, 200) pickerViewMode:mode updateBlock:^(MXPickerView *pickerView, MXPickerViewMode pickerViewMode, id uiDatePickerOrUIPickerView) { 252 | UIDatePicker *datePicker = (UIDatePicker *)uiDatePickerOrUIPickerView; 253 | datePicker.minuteInterval = 1; 254 | switch (mode) { 255 | case MXPickerViewModeTime: { 256 | //前1小时 - 后1小时 257 | datePicker.date = [NSDate date]; 258 | datePicker.minimumDate = [NSDate dateWithTimeInterval:-1 * 60 * 60 sinceDate:datePicker.date]; 259 | datePicker.maximumDate = [NSDate dateWithTimeInterval:1 * 60 * 60 sinceDate:datePicker.date]; 260 | break; 261 | } 262 | case MXPickerViewModeDate: 263 | case MXPickerViewModeDateAndTime: { 264 | //前天 - 后天 265 | datePicker.date = [NSDate date]; 266 | datePicker.minimumDate = [NSDate dateWithTimeInterval:-24 * 60 * 60 sinceDate:datePicker.date]; 267 | datePicker.maximumDate = [NSDate dateWithTimeInterval:24 * 60 * 60 sinceDate:datePicker.date]; 268 | break; 269 | } 270 | case MXPickerViewModeCountDownTimer: { 271 | datePicker.countDownDuration = 60 * 10;//选中为10分钟 272 | break; 273 | } 274 | default: 275 | break; 276 | } 277 | //pickerView.toolBarPositionBottom = YES; //默认为`NO`,`返回`和`确定`按钮显示在顶部,当设为`YES`,`返回`和`确定`按钮显示在底部 278 | //pickerView.topTipBarTitles = @[@"开始日期", @"结束日期"]; //不为`nil`时,`toolBarPositionBottom`会自动设置为`YES`,底部显示`返回`和`确定`按钮,顶部显示`开始日期 - 结束日期` 279 | //pickerView.topTipBarH = 50; //`topTipBarTitles`一栏高度,默认是44 280 | //pickerView.toolBarH = 30; //`返回`和`确定`按钮一栏高度,默认是44 281 | //pickerView.backgroundColor = [UIColor lightGrayColor]; 282 | pickerView.doneButtonDidClickBlock = ^(MXPickerViewMode pickerViewMode, CGFloat countDownDuration, NSDate *date, id selectedTitleOrCustomModels) { 283 | NSLog(@"date: %@", date); 284 | }; 285 | }]; 286 | ``` 287 | 288 | -------------------------------------------------------------------------------- /MXPickerView/MXPickerView/MXPickerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // MXPickerView.m 3 | // 1008 - MXPickerView 4 | // 5 | // Created by Michael on 2018/10/8. 6 | // Copyright © 2018 Michael. All rights reserved. 7 | // 8 | 9 | #import "MXPickerView.h" 10 | #import "MXAnimation.h" 11 | 12 | #pragma mark - ----------------------- MXPickerModel ----------------- 13 | @interface MXPickerModel : NSObject 14 | 15 | @property (nonatomic, copy) NSString *title; 16 | 17 | + (instancetype)modelWithTitle:(NSString *)title; 18 | 19 | @end 20 | 21 | @implementation MXPickerModel 22 | 23 | + (instancetype)modelWithTitle:(NSString *)title { 24 | MXPickerModel *model = MXPickerModel.new; 25 | model.title = title; 26 | return model; 27 | } 28 | 29 | @end 30 | 31 | #pragma mark - ----------------------- MXPickerView --------------------- 32 | //RGBA 33 | #define MXPV_RGBA_FROM_HEX(rgbValue, trans) [UIColor \ 34 | colorWithRed:((float)((0x##rgbValue & 0xFF0000) >> 16))/255.0 \ 35 | green:((float)((0x##rgbValue & 0xFF00) >> 8))/255.0 \ 36 | blue:((float)(0x##rgbValue & 0xFF))/255.0 alpha:trans] 37 | //RGB 38 | #define MXPV_RGB_FROM_HEX(rgbValue) MXPV_RGBA_FROM_HEX(rgbValue, 1.0f) 39 | 40 | @interface MXPickerView () { 41 | __weak UIView *_maskView; 42 | __weak UIToolbar *_toolBar; 43 | NSArray *_topTipBarTitles; 44 | BOOL _isClassUIDatePicker; 45 | id _uiDatePickerOrUIPickerView; 46 | } 47 | 48 | @property(nonatomic, readwrite) MXPickerViewMode pickerViewMode; 49 | 50 | @end 51 | 52 | @implementation MXPickerView 53 | - (void)dealloc { 54 | } 55 | - (instancetype)initWithFrame:(CGRect)frame pickerViewMode:(MXPickerViewMode)pickerViewMode updateBlock:(void (^)(MXPickerView *pickerView, MXPickerViewMode pickerViewMode, id uiDatePickerOrUIPickerView))updateBlock { 56 | CGFloat safeH = 0; 57 | if (@available(iOS 11.0, *)) { 58 | safeH = [UIApplication sharedApplication].keyWindow.safeAreaInsets.bottom; 59 | } 60 | frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height - frame.size.height - safeH, frame.size.width, frame.size.height); 61 | self = [super initWithFrame:frame]; 62 | if (!self) return nil; 63 | self.backgroundColor = [UIColor colorWithWhite:0.96f alpha:1.0f]; 64 | _yearLocale = ![_yearLocale isKindOfClass:NSString.class] ? @"" : @"年"; 65 | _monthLocale = ![_monthLocale isKindOfClass:NSString.class] ? @"" : @"月"; 66 | _dayLocale = ![_dayLocale isKindOfClass:NSString.class] ? @"" : @"日"; 67 | _modelsM = @[].mutableCopy; 68 | _toolBarH = _topTipBarH = 44; 69 | _pickerViewMode = pickerViewMode; 70 | !updateBlock ?: updateBlock(self, _pickerViewMode, nil); 71 | [self calculateDateIfNeeded]; 72 | [self addCustomView]; 73 | [self addMaskView]; 74 | [[UIApplication sharedApplication].keyWindow addSubview:self]; 75 | 76 | UIDatePickerMode datePickerMode = UIDatePickerModeTime; 77 | switch (pickerViewMode) { 78 | case MXPickerViewModeMMDD_MMDD : 79 | case MXPickerViewModeMM_DD : 80 | case MXPickerViewModeMM_MM : 81 | case MXPickerViewModeMM: { 82 | NSCalendar *calendar = [NSCalendar currentCalendar]; 83 | //[calendar setFirstWeekday:1];//1.Sun. 2.Mon. 3.Thes. 4.Wed. 5.Thur. 6.Fri. 7.Sat. 84 | NSDateComponents *minComponents = !_minimumDate ? nil : [calendar components:NSCalendarUnitMonth fromDate:_minimumDate], *maxComponents = !_maximumDate ? nil : [calendar components:NSCalendarUnitMonth fromDate:_maximumDate]; 85 | NSInteger min = minComponents.month, max = maxComponents.month; 86 | NSMutableArray *mmModelsM = @[].mutableCopy; 87 | if (min > max) { 88 | for (NSInteger i = min; i < 12; i++) 89 | [mmModelsM addObject:[MXPickerModel modelWithTitle:[NSString stringWithFormat:@"%ld%@", i, _monthLocale]]]; 90 | min = 0; 91 | } 92 | for (NSInteger i = min%12; i <= max; i++) 93 | [mmModelsM addObject:[MXPickerModel modelWithTitle:[NSString stringWithFormat:@"%ld%@", i ?: 12, _monthLocale]]]; 94 | if (_showsDescending) mmModelsM = (id)mmModelsM.reverseObjectEnumerator.allObjects; 95 | if (pickerViewMode == MXPickerViewModeMM || pickerViewMode == MXPickerViewModeMM_MM) { 96 | [_modelsM addObject:mmModelsM]; 97 | pickerViewMode == MXPickerViewModeMM ?: [_modelsM addObject:mmModelsM.mutableCopy]; 98 | } else if (pickerViewMode == MXPickerViewModeMM_DD || pickerViewMode == MXPickerViewModeMMDD_MMDD) { 99 | //DD 100 | NSMutableArray *ddModelsM = @[].mutableCopy; 101 | for (NSInteger i = 1; i <= 31; i++) 102 | [ddModelsM addObject:[MXPickerModel modelWithTitle:[NSString stringWithFormat:@"%ld%@", i, _dayLocale]]]; 103 | if (_showsDescending) ddModelsM = (id)ddModelsM.reverseObjectEnumerator.allObjects; 104 | [_modelsM addObject:mmModelsM]; 105 | [_modelsM addObject:ddModelsM]; 106 | if (pickerViewMode == MXPickerViewModeMMDD_MMDD) { 107 | [_modelsM addObject:mmModelsM.mutableCopy]; 108 | [_modelsM addObject:ddModelsM.mutableCopy]; 109 | } 110 | } 111 | break; 112 | } 113 | case MXPickerViewModeDD: 114 | case MXPickerViewModeDD_DD: { 115 | NSInteger numberOfDays = [self numberOfDaysInTheMonthOfDate:_maximumDate]; 116 | NSCalendar *calendar = [NSCalendar currentCalendar]; 117 | //[calendar setFirstWeekday:1];//1.Sun. 2.Mon. 3.Thes. 4.Wed. 5.Thur. 6.Fri. 7.Sat. 118 | NSDateComponents *minComponents = !_minimumDate ? nil : [calendar components:NSCalendarUnitDay fromDate:_minimumDate], *maxComponents = !_maximumDate ? nil : [calendar components:NSCalendarUnitDay fromDate:_maximumDate]; 119 | NSInteger min = minComponents.day, max = maxComponents.day; 120 | NSMutableArray *modelsM = @[].mutableCopy; 121 | if (min > max) { 122 | for (NSInteger i = min; i < numberOfDays; i++) 123 | [modelsM addObject:[MXPickerModel modelWithTitle:[NSString stringWithFormat:@"%ld%@", i, _dayLocale]]]; 124 | min = 0; 125 | } 126 | for (NSInteger i = min%numberOfDays; i <= max; i++) 127 | [modelsM addObject:[MXPickerModel modelWithTitle:[NSString stringWithFormat:@"%ld%@", i ?: numberOfDays, _dayLocale]]]; 128 | if (_showsDescending) modelsM = (id)modelsM.reverseObjectEnumerator.allObjects; 129 | [_modelsM addObject:modelsM]; 130 | pickerViewMode == MXPickerViewModeDD ?: [_modelsM addObject:modelsM.mutableCopy]; 131 | break; 132 | } 133 | case MXPickerViewModeYYYY: 134 | case MXPickerViewModeYYYY_YYYY: 135 | case MXPickerViewModeYYYY_MM: 136 | case MXPickerViewModeYYYYMM_YYYYMM: 137 | case MXPickerViewModeYYYYMMDD_YYYYMMDD: { 138 | NSCalendar *calendar = [NSCalendar currentCalendar]; 139 | //[calendar setFirstWeekday:1];//1.Sun. 2.Mon. 3.Thes. 4.Wed. 5.Thur. 6.Fri. 7.Sat. 140 | NSDateComponents *minComponents = !_minimumDate ? nil : [calendar components:NSCalendarUnitYear fromDate:_minimumDate], *maxComponents = !_maximumDate ? nil : [calendar components:NSCalendarUnitYear fromDate:_maximumDate]; 141 | NSInteger min = minComponents.year, max = maxComponents.year; 142 | NSMutableArray *yyyyModelsM = @[].mutableCopy, *mmModelsM = @[].mutableCopy, *ddModelsM = @[].mutableCopy; 143 | //YYYY 144 | for (NSInteger i = min; i <= max; i++) { 145 | [yyyyModelsM addObject:[MXPickerModel modelWithTitle:[NSString stringWithFormat:@"%ld%@", i, _yearLocale]]]; 146 | } 147 | if (_showsDescending) yyyyModelsM = (id)yyyyModelsM.reverseObjectEnumerator.allObjects; 148 | //MM 149 | for (NSInteger i = 1; i <= 12; i++) 150 | [mmModelsM addObject:[MXPickerModel modelWithTitle:[NSString stringWithFormat:@"%ld%@", i, _monthLocale]]]; 151 | if (_showsDescending) mmModelsM = (id)mmModelsM.reverseObjectEnumerator.allObjects; 152 | //DD 153 | for (NSInteger i = 1; i <= 31; i++) 154 | [ddModelsM addObject:[MXPickerModel modelWithTitle:[NSString stringWithFormat:@"%ld%@", i, _dayLocale]]]; 155 | if (_showsDescending) ddModelsM = (id)ddModelsM.reverseObjectEnumerator.allObjects; 156 | if (pickerViewMode == MXPickerViewModeYYYY || pickerViewMode == MXPickerViewModeYYYY_YYYY) { 157 | [_modelsM addObject:yyyyModelsM]; 158 | pickerViewMode == MXPickerViewModeYYYY ?: [_modelsM addObject:yyyyModelsM.mutableCopy]; 159 | } else if (pickerViewMode == MXPickerViewModeYYYYMM_YYYYMM || pickerViewMode == MXPickerViewModeYYYY_MM) { 160 | [_modelsM addObject:yyyyModelsM]; 161 | [_modelsM addObject:mmModelsM]; 162 | if (pickerViewMode == MXPickerViewModeYYYYMM_YYYYMM) { 163 | [_modelsM addObject:yyyyModelsM.mutableCopy]; 164 | [_modelsM addObject:mmModelsM.mutableCopy]; 165 | } 166 | } else if (pickerViewMode == MXPickerViewModeYYYYMMDD_YYYYMMDD) { 167 | [_modelsM addObject:yyyyModelsM]; 168 | [_modelsM addObject:mmModelsM]; 169 | [_modelsM addObject:ddModelsM]; 170 | [_modelsM addObject:yyyyModelsM.mutableCopy]; 171 | [_modelsM addObject:mmModelsM.mutableCopy]; 172 | [_modelsM addObject:ddModelsM.mutableCopy]; 173 | } 174 | break; 175 | } 176 | case MXPickerViewModeDate: { 177 | _isClassUIDatePicker = YES; 178 | datePickerMode = UIDatePickerModeDate; 179 | break; 180 | } 181 | case MXPickerViewModeTime: { 182 | _isClassUIDatePicker = YES; 183 | datePickerMode = UIDatePickerModeTime; 184 | break; 185 | } 186 | case MXPickerViewModeDateAndTime: { 187 | _isClassUIDatePicker = YES; 188 | datePickerMode = UIDatePickerModeDateAndTime; 189 | break; 190 | } 191 | case MXPickerViewModeCountDownTimer: { 192 | _isClassUIDatePicker = YES; 193 | datePickerMode = UIDatePickerModeCountDownTimer; 194 | break; 195 | } 196 | default: 197 | break; 198 | } 199 | CGFloat y = _toolBarPositionBottom ? 0 : CGRectGetMaxY(_toolBar.frame), h = self.bounds.size.height - _toolBarH, topTipBarH = self.topTipBarH; 200 | if (topTipBarH) { 201 | y += topTipBarH; 202 | h -= y; 203 | [self addTopTipBarIfNeeded]; 204 | } 205 | if (_isClassUIDatePicker) { 206 | UIDatePicker *datePicker = _uiDatePickerOrUIPickerView = [UIDatePicker new]; 207 | datePicker.frame = CGRectMake(0, y, self.bounds.size.width, h); 208 | datePicker.datePickerMode = datePickerMode; 209 | [datePicker setTimeZone:[NSTimeZone defaultTimeZone]]; 210 | [datePicker setLocale:[[NSLocale alloc]initWithLocaleIdentifier:@"zh_Hans_CN"]]; 211 | [datePicker addTarget:self action:@selector(valueChanged:) forControlEvents:UIControlEventValueChanged]; 212 | [self addSubview:datePicker]; 213 | !updateBlock ?: updateBlock(self, _pickerViewMode, datePicker); 214 | datePicker.minimumDate ?: [datePicker setMinimumDate:_minimumDate]; 215 | datePicker.maximumDate ?: [datePicker setMaximumDate:_maximumDate]; 216 | } else { 217 | UIPickerView *pickerView = _uiDatePickerOrUIPickerView = UIPickerView.new; 218 | pickerView.dataSource = self; 219 | pickerView.delegate = self; 220 | pickerView.frame = CGRectMake(0, y, self.bounds.size.width, h); 221 | [self addSubview:pickerView]; 222 | !updateBlock ?: updateBlock(self, _pickerViewMode, pickerView); 223 | if (pickerViewMode == MXPickerViewModeCustom) { 224 | [_selectedTitleOrCustomModels enumerateObjectsUsingBlock:^(id customModel, NSUInteger idx, BOOL *stop) { 225 | NSUInteger scrollIdx = [self.modelsM[idx] indexOfObject:customModel]; 226 | NSAssert(scrollIdx != NSNotFound, @"%@ does not exist in %@", customModel, self.modelsM); 227 | [pickerView selectRow:scrollIdx inComponent:idx animated:YES]; 228 | }]; 229 | } else { 230 | _selectedTitleOrCustomModels = [_selectedTitleOrCustomModels componentsSeparatedByString:@" "]; 231 | [_selectedTitleOrCustomModels enumerateObjectsUsingBlock:^(NSString *componentSelectedTitle, NSUInteger idx, BOOL *stop) { 232 | NSUInteger scrollIdx = [self.modelsM[idx] indexOfObjectPassingTest:^BOOL(MXPickerModel *model, NSUInteger idx, BOOL *stop) { 233 | return [model.title isEqualToString:componentSelectedTitle]; 234 | }]; 235 | NSAssert(scrollIdx != NSNotFound, @"%@ does not exist in %@", componentSelectedTitle, self.modelsM); 236 | [pickerView selectRow:scrollIdx inComponent:idx animated:YES]; 237 | }]; 238 | } 239 | } 240 | return self; 241 | } 242 | 243 | #pragma mark - add UI 244 | - (void)addCustomView { 245 | //toolBar.items 246 | UIBarButtonItem *cancelBarBtnItem = [[UIBarButtonItem alloc] initWithTitle:@"取消" style:UIBarButtonItemStyleDone target:self action:@selector(cancelBarButtonItemClicked:)]; 247 | UIBarButtonItem *flexibleBarItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; 248 | UIBarButtonItem *doneBarBtnItem = [[UIBarButtonItem alloc] initWithTitle:@"确定" style:UIBarButtonItemStyleDone target:self action:@selector(doneBarButtonItemClicked:)]; 249 | UIToolbar *toolBar = UIToolbar.new; 250 | toolBar.backgroundColor = [UIColor whiteColor]; 251 | [toolBar setShadowImage:UIImage.new forToolbarPosition:UIBarPositionAny]; 252 | if (!_toolBarPositionBottom) { 253 | cancelBarBtnItem.width = 60; 254 | doneBarBtnItem.width = 60; 255 | toolBar.frame = CGRectMake(0, 0, self.bounds.size.width, _toolBarH); 256 | toolBar.items = @[cancelBarBtnItem, flexibleBarItem, doneBarBtnItem]; 257 | } else { 258 | cancelBarBtnItem.width = self.bounds.size.width/2.0f; 259 | doneBarBtnItem.width = self.bounds.size.width/2.0f; 260 | toolBar.frame = CGRectMake(0, self.bounds.size.height - _toolBarH, self.bounds.size.width, _toolBarH); 261 | toolBar.items = @[cancelBarBtnItem, doneBarBtnItem]; 262 | } 263 | [self addSubview:_toolBar = toolBar]; 264 | [@[@(UIControlStateNormal), @(UIControlStateHighlighted)] enumerateObjectsUsingBlock:^(NSNumber *state, NSUInteger idx, BOOL *stop) { 265 | if (@available(iOS 9.0, *)) { 266 | [[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[MXPickerView.class]] setTitleTextAttributes:self.barBtnItemTitleTextAttributes forState:state.integerValue]; 267 | } else { 268 | [[UIBarButtonItem appearanceWhenContainedIn:MXPickerView.class, nil] setTitleTextAttributes:self.barBtnItemTitleTextAttributes forState:state.integerValue]; 269 | } 270 | }]; 271 | } 272 | 273 | - (void)addTopTipBarIfNeeded { 274 | if (!self.topTipBarTitles.count) return; 275 | NSMutableArray *barBtnItems = @[].mutableCopy; 276 | [self.topTipBarTitles enumerateObjectsUsingBlock:^(NSString *title, NSUInteger idx, BOOL *stop) { 277 | UIBarButtonItem *barBtnItem = [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStyleDone target:nil action:nil]; 278 | barBtnItem.width = self.bounds.size.width/self.topTipBarTitles.count; 279 | [barBtnItems addObject:barBtnItem]; 280 | }]; 281 | //toolBar.items 282 | UIToolbar *topTipBar = UIToolbar.new; 283 | topTipBar.userInteractionEnabled = NO; 284 | topTipBar.backgroundColor = [UIColor whiteColor]; 285 | topTipBar.frame = CGRectMake(0, 0, self.bounds.size.width, self.topTipBarH); 286 | topTipBar.items = barBtnItems; 287 | [topTipBar setShadowImage:UIImage.new forToolbarPosition:UIBarPositionAny]; 288 | [self addSubview:topTipBar]; 289 | [@[@(UIControlStateNormal), @(UIControlStateHighlighted)] enumerateObjectsUsingBlock:^(NSNumber *state, NSUInteger idx, BOOL *stop) { 290 | if (@available(iOS 9.0, *)) { 291 | [[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[MXPickerView.class]] setTitleTextAttributes:self.barBtnItemTitleTextAttributes forState:state.integerValue]; 292 | [[UILabel appearanceWhenContainedInInstancesOfClasses:@[MXPickerView.class]] setBackgroundColor:[UIColor redColor]]; 293 | } else { 294 | [[UIBarButtonItem appearanceWhenContainedIn:MXPickerView.class, nil] setTitleTextAttributes:self.barBtnItemTitleTextAttributes forState:state.integerValue]; 295 | } 296 | }]; 297 | } 298 | 299 | - (void)addMaskView { 300 | UIView *maskView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds]; 301 | maskView.backgroundColor = [UIColor colorWithWhite:0.3 alpha:0.5]; 302 | UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTapGestureInvoked:)]; 303 | [maskView addGestureRecognizer:singleTap]; 304 | [[UIApplication sharedApplication].keyWindow addSubview:maskView]; 305 | _maskView = maskView; 306 | } 307 | 308 | #pragma mark - private methods 309 | - (void)calculateDateIfNeeded { 310 | switch (_pickerViewMode) { 311 | case MXPickerViewModeCustom: 312 | NSAssert(!_selectedTitleOrCustomModels || [_selectedTitleOrCustomModels isKindOfClass:NSArray.class], @"when mode is MXPickerViewModeCustom, `selectedTitleOrCustomModels` should pass `customModels`, type of NSArray"); 313 | break; 314 | case MXPickerViewModeDD: 315 | case MXPickerViewModeDD_DD: 316 | case MXPickerViewModeMM: 317 | case MXPickerViewModeMM_MM: 318 | case MXPickerViewModeMM_DD: 319 | case MXPickerViewModeMMDD_MMDD: 320 | case MXPickerViewModeYYYY: 321 | case MXPickerViewModeYYYY_YYYY: 322 | case MXPickerViewModeYYYY_MM: 323 | case MXPickerViewModeYYYYMM_YYYYMM: 324 | case MXPickerViewModeYYYYMMDD_YYYYMMDD: 325 | NSAssert(!_selectedTitleOrCustomModels || ([_selectedTitleOrCustomModels isKindOfClass:NSString.class] && [_selectedTitleOrCustomModels componentsSeparatedByString:@" "]), @"when mode is for date such as `MXPickerViewModeDD`..., `selectedTitleOrCustomModels` should pass `selectedTitle`, type of NSString, joined by whitespace, format something like `12 12`"); 326 | break; 327 | case MXPickerViewModeTime: 328 | case MXPickerViewModeDate: 329 | case MXPickerViewModeDateAndTime: 330 | case MXPickerViewModeCountDownTimer: 331 | // when mode for `UIDatePicker`, such as `MXPickerViewModeDateAndTime`, `selectedTitleOrCustomModels` should not pass, always be nil! 332 | _selectedTitleOrCustomModels = nil; 333 | break; 334 | } 335 | if (_minimumDate && _maximumDate) { 336 | _deltaBetweenMaxAndMin = 0; 337 | return; 338 | } else { 339 | NSCalendar *currentCalendar = [NSCalendar currentCalendar]; 340 | switch (_pickerViewMode) { 341 | case MXPickerViewModeMM: 342 | case MXPickerViewModeMM_MM: 343 | case MXPickerViewModeMM_DD: 344 | case MXPickerViewModeMMDD_MMDD: { 345 | _deltaBetweenMaxAndMin %= 12; 346 | _deltaBetweenMaxAndMin = _deltaBetweenMaxAndMin ?: 12; 347 | if (_minimumDate) { 348 | NSDateComponents *components = [currentCalendar components:NSCalendarUnitYear | NSCalendarUnitMonth fromDate:_minimumDate]; 349 | components.month += _deltaBetweenMaxAndMin; 350 | if (components.month > 12) { 351 | if (_deltaBetweenMaxAndMin == 12) { 352 | components.month += _deltaBetweenMaxAndMin - 1; 353 | } 354 | components.month %= 12; 355 | } 356 | _maximumDate = [currentCalendar dateFromComponents:components]; 357 | } else if (_maximumDate) { 358 | NSDateComponents *components = [currentCalendar components:NSCalendarUnitYear | NSCalendarUnitMonth fromDate:_maximumDate]; 359 | components.month -= _deltaBetweenMaxAndMin; 360 | if (components.month <= 0) { 361 | if (_deltaBetweenMaxAndMin == 12) { 362 | components.month -= _deltaBetweenMaxAndMin - 1; 363 | } 364 | components.month %= 12; 365 | components.month += 12; 366 | } 367 | _minimumDate = [currentCalendar dateFromComponents:components]; 368 | } else if (!_minimumDate && !_maximumDate) { 369 | NSDateComponents *components = [currentCalendar components:NSCalendarUnitYear | NSCalendarUnitMonth fromDate:NSDate.date]; 370 | components.month = 1; 371 | _minimumDate = [currentCalendar dateFromComponents:components]; 372 | components.month = 12; 373 | _maximumDate = [currentCalendar dateFromComponents:components]; 374 | } 375 | break; 376 | } 377 | case MXPickerViewModeDD: 378 | case MXPickerViewModeDD_DD: { 379 | NSInteger numberOfDays = [self numberOfDaysInTheMonthOfDate:_maximumDate ?: (_minimumDate ?: NSDate.date)]; 380 | _deltaBetweenMaxAndMin %= numberOfDays; 381 | _deltaBetweenMaxAndMin = _deltaBetweenMaxAndMin ?: numberOfDays; 382 | if (_minimumDate) { 383 | NSDateComponents *components = [currentCalendar components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:_minimumDate]; 384 | components.day += _deltaBetweenMaxAndMin; 385 | if (components.day > numberOfDays) { 386 | if (_deltaBetweenMaxAndMin == numberOfDays) { 387 | components.day += _deltaBetweenMaxAndMin - 1; 388 | } 389 | components.day %= numberOfDays; 390 | } 391 | _maximumDate = [currentCalendar dateFromComponents:components]; 392 | } else if (_maximumDate) { 393 | NSDateComponents *components = [currentCalendar components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:_maximumDate]; 394 | components.day -= _deltaBetweenMaxAndMin; 395 | if (components.day <= 0) { 396 | if (_deltaBetweenMaxAndMin == numberOfDays) { 397 | components.day -= _deltaBetweenMaxAndMin - 1; 398 | } 399 | components.day %= numberOfDays; 400 | components.day += numberOfDays; 401 | } 402 | _minimumDate = [currentCalendar dateFromComponents:components]; 403 | } else if (!_minimumDate && !_maximumDate) { 404 | NSDateComponents *components = [currentCalendar components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:NSDate.date]; 405 | components.day = 1; 406 | _minimumDate = [currentCalendar dateFromComponents:components]; 407 | components.day = numberOfDays; 408 | _maximumDate = [currentCalendar dateFromComponents:components]; 409 | } 410 | break; 411 | } 412 | case MXPickerViewModeYYYY: 413 | case MXPickerViewModeYYYY_YYYY: 414 | case MXPickerViewModeYYYY_MM: 415 | case MXPickerViewModeYYYYMM_YYYYMM: 416 | case MXPickerViewModeYYYYMMDD_YYYYMMDD: { 417 | _deltaBetweenMaxAndMin = _deltaBetweenMaxAndMin ?: 100; 418 | if (_minimumDate) { 419 | NSDateComponents *components = [currentCalendar components:NSCalendarUnitYear fromDate:_minimumDate]; 420 | components.year += _deltaBetweenMaxAndMin; 421 | _maximumDate = [currentCalendar dateFromComponents:components]; 422 | } else if (_maximumDate) { 423 | NSDateComponents *components = [currentCalendar components:NSCalendarUnitYear fromDate:_maximumDate]; 424 | components.year -= _deltaBetweenMaxAndMin; 425 | _minimumDate = [currentCalendar dateFromComponents:components]; 426 | } else if (!_minimumDate && !_maximumDate) { 427 | NSDateComponents *components = [currentCalendar components:NSCalendarUnitYear fromDate:NSDate.date]; 428 | _maximumDate = [currentCalendar dateFromComponents:components]; 429 | components.year -= _deltaBetweenMaxAndMin; 430 | _minimumDate = [currentCalendar dateFromComponents:components]; 431 | } 432 | break; 433 | } 434 | default: 435 | break; 436 | } 437 | } 438 | } 439 | 440 | - (void)animationForShow:(BOOL)shouldShow { 441 | [self translateAnimationForShow:shouldShow]; 442 | [self alphaAnimatonForShow:shouldShow]; 443 | } 444 | 445 | - (void)translateAnimationForShow:(BOOL)shouldShow { 446 | [self.layer addAnimation:shouldShow ? [MXAnimation upFromKeyWindowAnimationWithDuration:0 addToView:self updateBlock:nil] : [MXAnimation downToKeyWindowAnimationWithDuration:0.2 addToView:self updateBlock:nil] forKey:nil]; 447 | } 448 | 449 | - (void)alphaAnimatonForShow:(BOOL)shouldShow { 450 | [_maskView.layer addAnimation:shouldShow ? [MXAnimation showAlphaAnimationWithDuration:0 updateBlock:nil] : [MXAnimation hideAlphaAnimationWithDuration:0 updateBlock:^(CAKeyframeAnimation *animation) { 451 | animation.animationDidStopBlock = ^(CAAnimation *anim, BOOL finished) { 452 | [self removeFromSuperview]; 453 | [self->_maskView removeFromSuperview]; 454 | self->_maskView = nil; 455 | }; 456 | }] forKey:nil]; 457 | } 458 | 459 | - (NSUInteger)numberOfDaysInTheMonthOfDate:(NSDate *)date { 460 | NSRange range = [[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:date]; 461 | return range.length; 462 | } 463 | 464 | #pragma mark - public 465 | + (instancetype)showWithSize:(CGSize)size pickerViewMode:(MXPickerViewMode)pickerViewMode updateBlock:(void (^)(MXPickerView *pickerView, MXPickerViewMode pickerViewMode, id uiDatePickerOrUIPickerView))updateBlock { 466 | MXPickerView *pickerView = [[MXPickerView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height) pickerViewMode:pickerViewMode updateBlock:updateBlock]; 467 | [pickerView animationForShow:YES]; 468 | return pickerView; 469 | } 470 | 471 | #pragma mark - override 472 | - (BOOL)respondsToSelector:(SEL)aSelector { 473 | NSString *aSelectorStr = NSStringFromSelector(aSelector); 474 | if ([aSelectorStr isEqualToString:NSStringFromSelector(@selector(pickerView:attributedTitleForRow:forComponent:))]) { 475 | return (BOOL)self.configPickerViewAttributedTitleForRowForComponentBlock; 476 | } else { 477 | return [super respondsToSelector:aSelector]; 478 | } 479 | } 480 | 481 | #pragma mark - getter 482 | - (CGFloat)topTipBarH { 483 | return self.topTipBarTitles.count ? _topTipBarH : 0; 484 | } 485 | 486 | - (NSArray *)topTipBarTitles { 487 | if (_topTipBarTitles.count) return _topTipBarTitles; 488 | return _topTipBarTitles = nil; 489 | } 490 | 491 | - (NSDictionary *)barBtnItemTitleTextAttributes { 492 | if (_barBtnItemTitleTextAttributes.count) return _barBtnItemTitleTextAttributes; 493 | return @{NSFontAttributeName: [UIFont systemFontOfSize:14], NSForegroundColorAttributeName: MXPV_RGB_FROM_HEX(333333)}; 494 | } 495 | 496 | - (CGFloat)componentWidth { 497 | if (_componentWidth) return _componentWidth; 498 | UIPickerView *pickerView = (id)_uiDatePickerOrUIPickerView; 499 | NSInteger numberOfComponents = [self numberOfComponentsInPickerView:pickerView]; 500 | return _componentWidth = (pickerView.bounds.size.width - (numberOfComponents - 1) * 5)/numberOfComponents; 501 | } 502 | 503 | - (CGFloat)componentRowHeight { 504 | if (_componentRowHeight) return _componentRowHeight; 505 | return _componentRowHeight = 40; 506 | } 507 | 508 | #pragma mark - setter 509 | - (void)setToolBarPositionBottom:(BOOL)toolBarPositionBottom { 510 | if (_topTipBarTitles.count) { 511 | _toolBarPositionBottom = YES; 512 | return; 513 | } 514 | _toolBarPositionBottom = toolBarPositionBottom; 515 | } 516 | 517 | - (void)setTopTipBarTitles:(NSArray *)topTipBarTitles { 518 | if (!topTipBarTitles.count) { 519 | _topTipBarTitles = nil; 520 | _topTipBarH = 0; 521 | return; 522 | } 523 | if ([_topTipBarTitles isEqualToArray:topTipBarTitles]) { 524 | return; 525 | } 526 | _topTipBarTitles = topTipBarTitles.copy; 527 | self.toolBarPositionBottom = YES; 528 | } 529 | 530 | #pragma mark - button actions 531 | - (void)cancelBarButtonItemClicked:(UIBarButtonItem *)barButtonItem { 532 | [self animationForShow:NO]; 533 | } 534 | 535 | - (void)doneBarButtonItemClicked:(UIBarButtonItem *)barButtonItem { 536 | [self animationForShow:NO]; 537 | if (_isClassUIDatePicker) { 538 | UIDatePicker *datePicker = (id)_uiDatePickerOrUIPickerView; 539 | CGFloat countDownDuration = datePicker.datePickerMode != UIDatePickerModeCountDownTimer ? 0 : datePicker.countDownDuration; 540 | NSDate *date = datePicker.datePickerMode != UIDatePickerModeCountDownTimer ? datePicker.date : nil; 541 | !_doneButtonDidClickBlock ?: _doneButtonDidClickBlock(_pickerViewMode, countDownDuration, date, nil); 542 | } else { 543 | if (_pickerViewMode == MXPickerViewModeCustom) { 544 | UIPickerView *pickerView = (id)_uiDatePickerOrUIPickerView; 545 | NSMutableArray *arrM = @[].mutableCopy; 546 | for (NSInteger i = 0, count = pickerView.numberOfComponents; i < count; i++) { 547 | id curModel = _modelsM[i][[pickerView selectedRowInComponent:i]]; 548 | [arrM addObject:curModel]; 549 | } 550 | !_doneButtonDidClickBlock ?: _doneButtonDidClickBlock(_pickerViewMode, 0, nil, arrM); 551 | } else { 552 | UIPickerView *pickerView = (id)_uiDatePickerOrUIPickerView; 553 | NSMutableString *strM = @"".mutableCopy; 554 | for (NSInteger i = 0, count = pickerView.numberOfComponents; i < count; i++) { 555 | NSString *curTitle = [_modelsM[i][[pickerView selectedRowInComponent:i]] title]; 556 | if (i == count - 1) { 557 | [strM appendFormat:@"%@", curTitle]; 558 | } else { 559 | [strM appendFormat:@"%@ ", curTitle]; 560 | } 561 | } 562 | !_doneButtonDidClickBlock ?: _doneButtonDidClickBlock(_pickerViewMode, 0, nil, strM); 563 | } 564 | } 565 | } 566 | 567 | #pragma mark - control actions 568 | - (void)valueChanged:(UIDatePicker *)datePicker { 569 | //NSLog(@"%@", datePicker.date); 570 | CGFloat countDownDuration = datePicker.datePickerMode != UIDatePickerModeCountDownTimer ? 0 : datePicker.countDownDuration; 571 | NSDate *date = datePicker.datePickerMode != UIDatePickerModeCountDownTimer ? datePicker.date : nil; 572 | !_datePickerDidChangeBlock ?: _datePickerDidChangeBlock(_pickerViewMode, countDownDuration, date, nil); 573 | } 574 | 575 | #pragma mark - gesture actions 576 | - (void)singleTapGestureInvoked:(UITapGestureRecognizer *)recognizer { 577 | [self animationForShow:NO]; 578 | } 579 | 580 | #pragma mark - UIPickerViewDataSource 581 | - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { 582 | return _modelsM.count; 583 | } 584 | 585 | - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { 586 | return [_modelsM[component] count]; 587 | } 588 | 589 | #pragma mark - UIPickerViewDelegate 590 | - (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component { 591 | return !_configPickerViewWidthForComponentBlock ? self.componentWidth : _configPickerViewWidthForComponentBlock(self, pickerView, component); 592 | } 593 | 594 | - (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component { 595 | return !_configPickerViewRowHeightForComponentBlock ? self.componentRowHeight : _configPickerViewRowHeightForComponentBlock(self, pickerView, component); 596 | } 597 | 598 | - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { 599 | if (_pickerViewMode == MXPickerViewModeYYYYMMDD_YYYYMMDD) { 600 | NSInteger dayCompnent = 0; 601 | if (component == 0 || component == 1 || component == 3 || component == 4) { 602 | NSString *year = nil, *month = nil; 603 | if (component == 1 || component == 4) { 604 | //curr component is month 605 | dayCompnent = component + 1; 606 | year = [_modelsM[component - 1][[pickerView selectedRowInComponent:component - 1]] title]; 607 | month = [_modelsM[component][row] title]; 608 | } else { 609 | //curr component is year 610 | dayCompnent = component + 2; 611 | year = [_modelsM[component][row] title]; 612 | month = [_modelsM[component + 1][[pickerView selectedRowInComponent:component + 1]] title]; 613 | } 614 | NSDateFormatter *fmt = NSDateFormatter.new; 615 | fmt.dateFormat = [NSString stringWithFormat:@"yyyy%@MM%@", _yearLocale, _monthLocale]; 616 | NSDate *date = [fmt dateFromString:[NSString stringWithFormat:@"%@%02ld", year, month.integerValue]]; 617 | if (!date) return; 618 | NSInteger numberOfDays = [self numberOfDaysInTheMonthOfDate:date]; 619 | //DD 620 | NSMutableArray *ddModelsM = @[].mutableCopy; 621 | for (NSInteger i = 1; i <= numberOfDays; i++) 622 | [ddModelsM addObject:[MXPickerModel modelWithTitle:[NSString stringWithFormat:@"%ld%@", i, _dayLocale]]]; 623 | if (_showsDescending) ddModelsM = (id)ddModelsM.reverseObjectEnumerator.allObjects; 624 | [_modelsM[dayCompnent] removeAllObjects]; 625 | _modelsM[dayCompnent] = ddModelsM; 626 | [pickerView reloadComponent:dayCompnent]; 627 | } 628 | } else if (_pickerViewMode == MXPickerViewModeMMDD_MMDD || _pickerViewMode == MXPickerViewModeMM_DD) { 629 | if (component == 0 || component == 2) { 630 | NSInteger dayCompnent = component + 1; 631 | NSString *year = [NSString stringWithFormat:@"%ld", [NSCalendar.currentCalendar components:NSCalendarUnitYear fromDate:NSDate.date].year], *month = [_modelsM[component][row] title]; 632 | //curr component is month 633 | NSDateFormatter *fmt = NSDateFormatter.new; 634 | fmt.dateFormat = [NSString stringWithFormat:@"yyyy%@MM%@", _yearLocale, _monthLocale]; 635 | NSDate *date = [fmt dateFromString:[NSString stringWithFormat:@"%@%@%ld%@", year, _yearLocale, month.integerValue, _monthLocale]]; 636 | if (!date) return; 637 | NSInteger numberOfDays = [self numberOfDaysInTheMonthOfDate:date]; 638 | //DD 639 | NSMutableArray *ddModelsM = @[].mutableCopy; 640 | for (NSInteger i = 1; i <= numberOfDays; i++) 641 | [ddModelsM addObject:[MXPickerModel modelWithTitle:[NSString stringWithFormat:@"%ld%@", i, _dayLocale]]]; 642 | if (_showsDescending) ddModelsM = (id)ddModelsM.reverseObjectEnumerator.allObjects; 643 | [_modelsM[dayCompnent] removeAllObjects]; 644 | _modelsM[dayCompnent] = ddModelsM; 645 | [pickerView reloadComponent:dayCompnent]; 646 | } 647 | } 648 | !_datePickerDidChangeBlock ?: _datePickerDidChangeBlock(_pickerViewMode, 0, 0, [_modelsM[component][row] title]); 649 | } 650 | 651 | - (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view { 652 | UIView *reusingView = nil; 653 | if (_configPickerViewViewForRowForComponentBlock) { 654 | return _configPickerViewViewForRowForComponentBlock(self, pickerView, row, component, view); 655 | } else { 656 | if (!reusingView) { 657 | UILabel *reusingLabel = UILabel.new; 658 | reusingLabel.textAlignment = NSTextAlignmentCenter; 659 | reusingLabel.font = [UIFont systemFontOfSize:17]; 660 | reusingView = reusingLabel; 661 | } 662 | if (_configPickerViewAttributedTitleForRowForComponentBlock) { 663 | NSAttributedString *attrTitle = _configPickerViewAttributedTitleForRowForComponentBlock(self, pickerView, row, component); 664 | ((UILabel *)reusingView).attributedText = ![attrTitle isKindOfClass:NSAttributedString.class] ? nil : attrTitle; 665 | } else if (_configPickerViewTitleForRowForComponentBlock) { 666 | NSString *title = !_configPickerViewTitleForRowForComponentBlock ? nil : _configPickerViewTitleForRowForComponentBlock(self, pickerView, row, component); 667 | ((UILabel *)reusingView).text = ![title isKindOfClass:NSString.class] ? nil : title; 668 | } else { 669 | MXPickerModel *model = _modelsM[component][row]; 670 | ((UILabel *)reusingView).text = ![model isKindOfClass:MXPickerModel.class] ? nil : model.title; 671 | } 672 | !_configPickerViewContentLabelAppearanceForRowForComponentBlock ?: _configPickerViewContentLabelAppearanceForRowForComponentBlock(self, pickerView, (id)reusingView, row, component); 673 | } 674 | return reusingView; 675 | } 676 | 677 | @end 678 | --------------------------------------------------------------------------------