├── 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 |
--------------------------------------------------------------------------------