├── .gitignore ├── README.md ├── VCPicker ├── VCPicker.h └── sources │ ├── VCPickerViewController.h │ └── VCPickerViewController.m └── VCPickerDev ├── VCPickerDev.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── VCPickerDev ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── MYViewController.h ├── MYViewController.m ├── ViewController.h ├── ViewController.m └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | ## System 2 | .DS_Store 3 | 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | xcuserdata/ 8 | 9 | # Add this line if you want to avoid checking in source code from the Xcode workspace 10 | 11 | 12 | ## Build generated 13 | build/ 14 | DerivedData/ 15 | 16 | ## Various settings 17 | *.pbxuser 18 | !default.pbxuser 19 | *.mode1v3 20 | !default.mode1v3 21 | *.mode2v3 22 | !default.mode2v3 23 | *.perspectivev3 24 | !default.perspectivev3 25 | 26 | ## Other 27 | *.moved-aside 28 | *.xccheckout 29 | *.xcscmblueprint 30 | 31 | ## Obj-C/Swift specific 32 | *.hmap 33 | *.ipa 34 | *.dSYM.zip 35 | *.dSYM 36 | 37 | # CocoaPods 38 | # 39 | # We recommend against adding the Pods directory to your .gitignore. However 40 | # you should judge for yourself, the pros and cons are mentioned at: 41 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 42 | # 43 | Pods/ 44 | # TabaoEnv 45 | PodMainDependencies/ 46 | #Podfile.lock 47 | #/Example/Podfile.lock 48 | #keep the podfile.lock and use pod install 49 | 50 | # TaobaoEnv 51 | PodMainDependencies 52 | 53 | 54 | # Carthage 55 | # 56 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 57 | # Carthage/Checkouts 58 | 59 | Carthage/Build 60 | 61 | # fastlane 62 | # 63 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 64 | # screenshots whenever they are needed. 65 | # For more information about the recommended setup visit: 66 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 67 | 68 | fastlane/report.xml 69 | fastlane/Preview.html 70 | fastlane/screenshots/**/*.png 71 | fastlane/test_output 72 | 73 | # Code Injection 74 | # 75 | # After new code Injection tools there's a generated folder /iOSInjectionProject 76 | # https://github.com/johnno1962/injectionforxcode 77 | 78 | iOSInjectionProject/ 79 | 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | > 如需运行 demo,可直接 clone 或下载后打开 VCPickerDev 文件夹下的工程 4 | 5 | 6 | # VCPicker 7 | 8 | Find and navi to your controller fast 9 | 10 | # How to use? 11 | add the code below in the appDidFinishLaunch method, with your class prefix 12 | 13 | ```Obbjective-C 14 | // it's recommended to pass a class prefix array to ignore unused classes 15 | [VCPickerViewController activateWhenDebugWithClassPrefixes:@[@"your_class_prefix"]]; 16 | ``` 17 | 18 | also see the article 19 | [开发利器:控制器传送门(已通过半年使用和上线验证,附demo)](http://www.jianshu.com/p/60357c77a9ed) 20 | 21 | # 0 背景 22 | 在项目早期开发阶段,还不需要接入后台网络数据,主要工作的界面的开发。 23 | 随着业务页面的深入,要查看二级、三级页面的效果就需要编译后跳转两三次甚至更多,不断地重复这个过程相对来说,就没有可以直接启动就查看那个页面来得那么高效。 24 | 25 | # 1 解决方案 26 | ## 1.1 简单粗暴的方式 27 | 常见的做法是在application:didFinishLaunchWithOptions:时直接push到想要到达的页面,如下: 28 | ``` objective-c 29 | // 获取到可以展示的容器控制器 30 | UINavigationViewController *naviVC = [self getNavigationVC]; 31 | // 创建想要跳转的控制器 32 | TargetViewController *targetVC = [[TargetViewController alloc] init]; 33 | // 跳转到目标页面 34 | [naviVC pushViewController:targetVC animated:YES]; 35 | ``` 36 | 这样可以在程序启动时便捷地跳转了,但在多个工程师协同合作时有一个潜在的问题 37 | - 是会污染其他同事的代码 38 | - 而多个同事都写了这样的便捷页面跳转而不小心提交到公共代码库就会出代码冲突 39 | 40 | ## 1.2 传送门方案VCPicker 41 | 为了方便每个协同开发的工程师跳转任意页面,一个更效率的方式有一个入口可以随时找到某一个ViewController页面的类,跳转过去即可,顺着这个思路需要做两个事情: 42 | - 找到工程里所有的ViewController控制器类 43 | - 设置一个统一的入口 44 | 45 | 实现的途径: 46 | - 利用```objc-runtime```机制获取项目中所有的类,再筛选出所有UIViewController的子类,以字符串的形式保存类名,以列表的形式展现,当点击某一个类时,通过类初始化一个实例即可实现目标页面的跳转 47 | ``` objective-c 48 | Class *classes = NULL; 49 | int numClasses = objc_getClassList(NULL, 0); 50 | ``` 51 | - 传送门入口的设计,最初是想通过摇一摇来实现从而不影响原有UI的效果,但是不便于模拟器上的使用,所以借鉴了苹果的辅助手势```Assist touch```悬浮球设计,在程序的keyWindow上悬浮一个可以挪动的小球从而在编译进入程序后可以第一时间点击查看控制器列表选择想要跳转的控制器。 52 | ``` 53 | // 创建悬浮效果 54 | [[UIDynamicAnimator alloc] initWithReferenceView:self.superview]; 55 | ``` 56 | # 2 优化 57 | 在使用的过程中逐步衍生并优化了一些有用的功能 58 | ## 2.1 使用class-prefix 59 | 由于通过runtime获取到的类有很多是系统的私有类,甚至不响应NSObject协议的,在操作这些类时则会非常危险,此外一些UI的控制器类(比如图片选择、通讯录获取)是需要权限才能访问和创建实例的,而我们实际的项目中一般都有类前缀```class-prefix```(外包的同学不服....),通过类前缀可以快速地筛选出实际项目中的业务页面 60 | ## 2.2 展示方式 61 | 根据具体的业务场景基本上分为带导航和不带导航两种,因此使用presentViewController的方法,一种会创建导航控制器后present,另一种则是直接present; 62 | ## 2.3 获取title 63 | 有的小伙伴提出,看到的是茫茫的一片类名而不知道业务title不方便同事之间学习,通过分析大部分都会在viewDidLoad方法设置title,或者navigationItem.title或者tabbarItem.title,因此需要实例化一个控制器对象,尝试调用viewDidLoad方法,实践证明如此是不安全的,一方面是viewDidLoad是控制器的生命周期方法原则上是父类调用的,再者很多同学还在viewDidLoad进行了KVO和通知的监听的监听,手动调用viewDidLoad会导致重复监听的问题,而调用[controller view]方法则可以完美地解决这个问题,调用[controller view]方法会在内部依次触发[controller loadView]和[controller viewDidLoad]方法,之后就能获取到想要的title信息;此外,在实例化对象调用view属性触发viewDidLoad时可能因为初始化参数不足的问题抛出异常,因此需要在此处代码块进行@try-catch保护,并保存异常信息提醒当前页面存在潜在异常。 64 | ``` objective-c 65 | // 创建实例,获取title 66 | UIViewController *controller = nil; 67 | NSMutableDictionary *dic = [NSMutableDictionary dictionary]; 68 | 69 | @try { 70 | if (_needTitle) { 71 | controller = [[NSClassFromString(className) alloc] init]; // nil 72 | [controller view]; // to active viewDidLoad so we can get conroller.title 73 | } 74 | 75 | } @catch (NSException *exception) { 76 | NSLog(@"[VCPicker <%@> exception: %@]", className, exception); 77 | dic[kErrorKey] = exception.description; 78 | 79 | } @finally { 80 | dic[kNameKey] = className; 81 | dic[kTitleKey] = controller.title ?: (controller.navigationItem.title ?: (controller.tabBarItem.title ?: className)); 82 | [self refreshHistoryForControllerInfo:dic]; 83 | [array addObject:dic]; 84 | } 85 | ``` 86 | # 2.4 增加历史记录和搜索功能 87 | 从众多的类列表中,经过排序可以方便查找,更方便的方法是提供搜索功能,下次再进入时如果保存了历史记录就更好了,所以这些都要有,所以都有了 88 | 89 | ### 感谢开发过程中Zoro和Ace同学的极好的建议 90 | 在实际开发项目中进行应用,内部实现使用```DEBUG```宏进行预编译的判断,确保上线时不会出问题,使用VCPicker不用移除也可以正常审核上线App store。 91 |  92 | # 3 效果图 93 | 94 | ![传送门悬浮球效果](http://upload-images.jianshu.io/upload_images/73339-ea9a99f28cf83ae1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 95 | 96 | ![类列表效果](http://upload-images.jianshu.io/upload_images/73339-adc420ddd7718458.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 97 | 98 | ![页面展示效果](http://upload-images.jianshu.io/upload_images/73339-d4707d87c372a718.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 99 | 100 | -------------------------------------------------------------------------------- /VCPicker/VCPicker.h: -------------------------------------------------------------------------------- 1 | // 2 | // VCPicker.h 3 | // VCPicker 4 | // 5 | // Created by beforeold on 2022/8/25. 6 | // Copyright © 2022 Yundi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | #import "VCPickerViewController.h" 14 | 15 | NS_ASSUME_NONNULL_END 16 | -------------------------------------------------------------------------------- /VCPicker/sources/VCPickerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // VCPickerViewController.h 3 | // VCPicker 4 | // 5 | // Created by beforeold on 16/3/24. 6 | // 7 | 8 | #import 9 | 10 | #ifdef DEBUG 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @protocol VCPickerCustomPresenting 15 | 16 | @optional 17 | /// 实现此方法,自定义展示的行为 18 | + (void)vcpicker_customShow; 19 | 20 | 21 | /// 自定义需要 present 的控制器 22 | + (__kindof UIViewController *)vcpicker_customViewController; 23 | 24 | @end 25 | 26 | /** 27 | * Find and navigate to your viewController fast 28 | * 用此类搜索和选择你想要跳转的控制器 29 | * 30 | */ 31 | @interface VCPickerViewController : UIViewController 32 | 33 | 34 | /** 35 | * activate VCPicker 36 | * 启用 VCPicker 37 | */ 38 | + (void)activateWhenDebug; 39 | 40 | /** 41 | * activate for prefixes, more efficiently,启用 VCPicker,携带类前缀信息,查找工程内控制器的效率更高 42 | * 43 | * @param prefixes class prefixes, can be nil 类前缀,可为nil 44 | */ 45 | + (void)activateWhenDebugWithClassPrefixes:(NSArray *_Nullable)prefixes; 46 | 47 | /// 显示 exceptArray 外的所有 prefix 的控制器 48 | + (void)activateWhenDebugWithClassPrefixes:(NSArray *_Nullable)prefixes except:(NSArray *_Nullable)exceptArray; 49 | 50 | + (void)activateWhenDebugWithClassPrefixes:(NSArray *_Nullable)prefixes 51 | except:(NSArray *_Nullable)exceptArray 52 | needTitle:(BOOL)needTitle; 53 | 54 | @end 55 | 56 | NS_ASSUME_NONNULL_END 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /VCPicker/sources/VCPickerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // VCPickerViewController.m 3 | // VCPicker 4 | // 5 | // Created by beforeold on 16/3/24. 6 | // 7 | 8 | #import "VCPickerViewController.h" 9 | #import 10 | 11 | #pragma mark - FloatingView 12 | 13 | #ifdef DEBUG 14 | 15 | static const CGFloat kFloatingLength = 30.0; 16 | static const CGFloat kScreenPadding = 0.5 * kFloatingLength; 17 | 18 | typedef NS_ENUM(NSInteger, VCPickerShowType) { 19 | VCPickerShowTypePresentNavi, // 默认用一个导航控制器 present 出来 20 | VCPickerShowTypePresent, // 直接 present 21 | VCPickerShowTypePush, // 在当前业务页面向前 push 22 | }; 23 | 24 | @interface VCPickerFloatingView : UIView 25 | 26 | @property (nonatomic, assign) CGPoint startPoint; //触摸起始点 27 | @property (nonatomic, assign) CGPoint endPoint; //触摸结束点 28 | @property (nonatomic, strong) UIView *backgroundViewForHightlight; //背景视图 29 | @property (nonatomic, strong) UIDynamicAnimator *animator; //物理仿真动画 30 | 31 | @property (nonatomic, copy) dispatch_block_t floatingBlock; 32 | 33 | @end 34 | 35 | @implementation VCPickerFloatingView 36 | // 初始化 37 | - (instancetype)initWithFrame:(CGRect)frame{ 38 | frame.size.width = kFloatingLength; 39 | frame.size.height = kFloatingLength; 40 | if (self = [super initWithFrame:frame]) { 41 | //初始化背景视图 42 | _backgroundViewForHightlight = [[UIView alloc] initWithFrame:self.bounds]; 43 | _backgroundViewForHightlight.layer.cornerRadius = _backgroundViewForHightlight.frame.size.width / 2; 44 | _backgroundViewForHightlight.clipsToBounds = YES; 45 | _backgroundViewForHightlight.backgroundColor = [UIColor colorWithRed:35/255.0 green:167/255.0 blue:67/255.0 alpha:1]; 46 | _backgroundViewForHightlight.userInteractionEnabled = NO; 47 | [self addSubview:_backgroundViewForHightlight]; 48 | 49 | // 初始化背景视图 50 | CGFloat padding = 5; 51 | CGRect contentFrame = CGRectMake(padding, padding, CGRectGetWidth(self.frame) - 2 * padding, CGRectGetHeight(self.frame) - 2 * padding); 52 | UIView * contentView = [[UIView alloc] initWithFrame:contentFrame]; 53 | contentView.layer.cornerRadius = contentView.frame.size.width / 2; 54 | contentView.clipsToBounds = YES; 55 | contentView.backgroundColor = [UIColor colorWithRed:35/255.0 green:167/255.0 blue:67/255.0 alpha:1]; 56 | contentView.userInteractionEnabled = NO; 57 | contentView.alpha = 0.7; 58 | [self addSubview:contentView]; 59 | 60 | // 将正方形的view变成圆形 61 | self.layer.cornerRadius = kFloatingLength / 2; 62 | self.alpha = 0.7; 63 | 64 | // 开启呼吸动画 65 | // [self highlightAnimation]; 66 | } 67 | 68 | return self; 69 | } 70 | 71 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 72 | //得到触摸点 73 | UITouch *startTouch = [touches anyObject]; 74 | //返回触摸点坐标 75 | self.startPoint = [startTouch locationInView:self.superview]; 76 | // 移除之前的所有行为 77 | [self.animator removeAllBehaviors]; 78 | } 79 | 80 | // 触摸移动 81 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ 82 | //得到触摸点 83 | UITouch *startTouch = [touches anyObject]; 84 | //将触摸点赋值给touchView的中心点 也就是根据触摸的位置实时修改view的位置 85 | self.center = [startTouch locationInView:self.superview]; 86 | } 87 | 88 | // 结束触摸 89 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ 90 | //得到触摸结束点 91 | UITouch *endTouch = [touches anyObject]; 92 | //返回触摸结束点 93 | self.endPoint = [endTouch locationInView:self.superview]; 94 | //判断是否移动了视图 (误差范围5) 95 | CGFloat errorRange = 5; 96 | if (( self.endPoint.x - self.startPoint.x >= -errorRange && 97 | self.endPoint.x - self.startPoint.x <= errorRange ) && 98 | ( self.endPoint.y - self.startPoint.y >= -errorRange && 99 | self.endPoint.y - self.startPoint.y <= errorRange )) 100 | { 101 | // 未移动,调用打开视图控制器方法 102 | !self.floatingBlock ?: self.floatingBlock(); 103 | 104 | } else { 105 | //移动 106 | self.center = self.endPoint; 107 | //计算距离最近的边缘 吸附到边缘停靠 108 | CGFloat superwidth = self.superview.bounds.size.width; 109 | CGFloat superheight = self.superview.bounds.size.height; 110 | CGFloat endX = self.endPoint.x; 111 | CGFloat endY = self.endPoint.y; 112 | CGFloat topRange = endY;//上距离 113 | CGFloat bottomRange = superheight - endY;//下距离 114 | CGFloat leftRange = endX;//左距离 115 | CGFloat rightRange = superwidth - endX;//右距离 116 | //比较上下左右距离 取出最小值 117 | CGFloat minRangeTB = topRange > bottomRange ? bottomRange : topRange;//获取上下最小距离 118 | CGFloat minRangeLR = leftRange > rightRange ? rightRange : leftRange;//获取左右最小距离 119 | CGFloat minRange = minRangeTB > minRangeLR ? minRangeLR : minRangeTB;//获取最小距离 120 | //判断最小距离属于上下左右哪个方向 并设置该方向边缘的point属性 121 | CGPoint minPoint = CGPointZero; 122 | if (minRange == topRange) { 123 | //上 124 | endX = endX - kScreenPadding < 0 ? kScreenPadding : endX; 125 | endX = endX + kScreenPadding > superwidth ? superwidth - kScreenPadding : endX; 126 | minPoint = CGPointMake(endX , 0 + kScreenPadding); 127 | } else if(minRange == bottomRange){ 128 | //下 129 | endX = endX - kScreenPadding < 0 ? kScreenPadding : endX; 130 | endX = endX + kScreenPadding > superwidth ? superwidth - kScreenPadding : endX; 131 | minPoint = CGPointMake(endX , superheight - kScreenPadding); 132 | 133 | } else if(minRange == leftRange){ 134 | //左 135 | endY = endY - kScreenPadding < 0 ? kScreenPadding : endY; 136 | endY = endY + kScreenPadding > superheight ? superheight - kScreenPadding : endY; 137 | minPoint = CGPointMake(0 + kScreenPadding , endY); 138 | 139 | } else if(minRange == rightRange){ 140 | //右 141 | endY = endY - kScreenPadding < 0 ? kScreenPadding : endY; 142 | endY = endY + kScreenPadding > superheight ? superheight - kScreenPadding : endY; 143 | minPoint = CGPointMake(superwidth - kScreenPadding , endY); 144 | } 145 | 146 | //添加吸附物理行为 147 | UIAttachmentBehavior *attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self 148 | attachedToAnchor:minPoint]; 149 | [attachmentBehavior setLength:0]; 150 | [attachmentBehavior setDamping:0.1]; 151 | [attachmentBehavior setFrequency:5]; 152 | [self.animator addBehavior:attachmentBehavior]; 153 | } 154 | } 155 | 156 | // UIDynamicAnimatorDelegate 157 | - (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator{ 158 | 159 | } 160 | 161 | // LazyLoading 162 | - (UIDynamicAnimator *)animator { 163 | if (!_animator) { 164 | // 创建物理仿真器(ReferenceView : 仿真范围) 165 | _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.superview]; 166 | _animator.delegate = self; 167 | } 168 | 169 | return _animator; 170 | } 171 | 172 | // BreathingAnimation 呼吸动画 173 | - (void)highlightAnimation { 174 | [UIView animateWithDuration:1.5f 175 | animations:^ 176 | { 177 | self.backgroundViewForHightlight.backgroundColor = [self.backgroundViewForHightlight.backgroundColor colorWithAlphaComponent:0.1f]; 178 | } 179 | completion:^(BOOL finished) 180 | { 181 | [self highlightAnimation]; 182 | }]; 183 | } 184 | 185 | - (void)darkAnimation { 186 | [UIView animateWithDuration:1.5f 187 | animations:^ 188 | { 189 | self.backgroundViewForHightlight.backgroundColor = [self.backgroundViewForHightlight.backgroundColor colorWithAlphaComponent:0.6f]; 190 | } 191 | completion:^(BOOL finished) 192 | { 193 | [self highlightAnimation]; 194 | }]; 195 | } 196 | 197 | @end 198 | 199 | #pragma mark - VCPickerCell 200 | static NSString *const kNameKey = @"kNameKey"; 201 | static NSString *const kTitleKey = @"kTitleKey"; 202 | static NSString *const kErrorKey = @"kErrorKey"; 203 | @interface VCPickerCell : UITableViewCell 204 | 205 | @property (nonatomic, copy) void(^presentClick)(void); 206 | @property (nonatomic, copy) void(^presentNaviClick)(void); 207 | @property (nonatomic, copy) void(^presentErrorClick)(NSString *title, NSString *msg); 208 | 209 | - (void)updateUIWithModel:(NSDictionary *)model; 210 | 211 | @end 212 | 213 | @interface VCPickerCell() 214 | 215 | @property (nonatomic, strong) UIButton *presentButton; 216 | @property (nonatomic, strong) UIButton *presentNaviButton; 217 | @property (nonatomic, strong) UILabel *titleLabel; 218 | @property (nonatomic, strong) UILabel *detailLabel; 219 | @property (nonatomic, strong) UILabel *errorLabel; 220 | 221 | @property (nonatomic, strong) NSDictionary *model; 222 | 223 | @end 224 | 225 | @implementation VCPickerCell 226 | 227 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { 228 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 229 | if(self) { 230 | [self.contentView addSubview:self.presentButton]; 231 | [self.contentView addSubview:self.presentNaviButton]; 232 | [self.contentView addSubview:self.titleLabel]; 233 | [self.contentView addSubview:self.detailLabel]; 234 | [self.contentView addSubview:self.errorLabel]; 235 | } 236 | 237 | return self; 238 | } 239 | 240 | - (void)layoutSubviews { 241 | [super layoutSubviews]; 242 | 243 | CGFloat buttonWidth = 45; 244 | CGFloat buttonPadding = 10; 245 | CGFloat horizontalMarigin = 15; 246 | CGFloat buttonVerticalMargin = 12; 247 | CGFloat presentNaviWidth = 0.01; 248 | CGFloat heightRatio = 0.75; 249 | CGFloat errorLabelWidth = 20; 250 | 251 | self.presentButton.frame = CGRectMake(horizontalMarigin, 252 | buttonVerticalMargin, 253 | buttonWidth, 254 | CGRectGetHeight(self.contentView.frame) - 2*buttonVerticalMargin); 255 | self.presentNaviButton.frame = CGRectMake(CGRectGetMaxX(self.presentButton.frame) + buttonPadding, 256 | buttonVerticalMargin, 257 | presentNaviWidth, 258 | CGRectGetHeight(self.contentView.frame) - 2*buttonVerticalMargin); 259 | 260 | self.errorLabel.frame = CGRectMake(CGRectGetWidth(self.contentView.frame) - horizontalMarigin - errorLabelWidth, 261 | (CGRectGetHeight(self.contentView.frame) - errorLabelWidth) *0.5, 262 | errorLabelWidth, 263 | errorLabelWidth); 264 | self.errorLabel.layer.cornerRadius = 0.5*errorLabelWidth; 265 | 266 | CGFloat titleX = CGRectGetMaxX(self.presentNaviButton.frame) + buttonPadding; 267 | CGFloat titleWidth = CGRectGetMinX(self.errorLabel.frame) - buttonPadding - titleX; 268 | self.titleLabel.frame = CGRectMake(titleX, 269 | 0, 270 | titleWidth, 271 | self.contentView.frame.size.height*heightRatio); 272 | 273 | self.detailLabel.frame = CGRectMake(CGRectGetMinX(self.titleLabel.frame), 274 | CGRectGetMaxY(self.titleLabel.frame), 275 | CGRectGetWidth(self.titleLabel.frame), 276 | (1-heightRatio)*self.contentView.frame.size.height); 277 | } 278 | 279 | - (void)updateUIWithModel:(NSDictionary *)classInfo { 280 | _model = classInfo; 281 | 282 | NSString *classTitle = classInfo[kTitleKey]; 283 | NSString *className = classInfo[kNameKey]; 284 | NSString *classError = classInfo[kErrorKey]; 285 | 286 | self.titleLabel.text = classTitle; 287 | self.detailLabel.text = className; 288 | self.errorLabel.text = classError ? @"i" : @">"; 289 | UIColor *errorColor = [UIColor colorWithRed:255/255.0 green:70/255.0 blue:1/255.0 alpha:1]; 290 | self.errorLabel.layer.backgroundColor = classError ? errorColor.CGColor : [UIColor whiteColor].CGColor; 291 | self.errorLabel.textColor = classError ? [UIColor whiteColor] : [UIColor grayColor]; 292 | } 293 | 294 | - (UIButton *)presentButton { 295 | if (!_presentButton) { 296 | _presentButton = [UIButton buttonWithType:UIButtonTypeCustom]; 297 | _presentButton.titleLabel.font = [UIFont systemFontOfSize:11]; 298 | _presentButton.backgroundColor = [UIColor colorWithRed:35/255.0 green:167/255.0 blue:67/255.0 alpha:1]; 299 | _presentButton.layer.cornerRadius = 5.0; 300 | [_presentButton addTarget:self action:@selector(presentClick:) forControlEvents:UIControlEventTouchUpInside]; 301 | [_presentButton setTitle:@"Pres" forState:UIControlStateNormal]; 302 | [_presentButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 303 | } 304 | 305 | return _presentButton; 306 | } 307 | 308 | - (UIButton *)presentNaviButton { 309 | if (!_presentNaviButton) { 310 | _presentNaviButton = [UIButton buttonWithType:UIButtonTypeCustom]; 311 | _presentNaviButton.backgroundColor = [UIColor colorWithRed:35/255.0 green:167/255.0 blue:67/255.0 alpha:1]; 312 | _presentNaviButton.layer.cornerRadius = 5.0; 313 | _presentNaviButton.titleLabel.font = [UIFont systemFontOfSize:10]; 314 | [_presentNaviButton addTarget:self action:@selector(presentNaviClick:) forControlEvents:UIControlEventTouchUpInside]; 315 | [_presentNaviButton setTitle:@"PresNavi" forState:UIControlStateNormal]; 316 | [_presentNaviButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 317 | } 318 | 319 | return _presentNaviButton; 320 | } 321 | 322 | - (UILabel *)titleLabel { 323 | if (!_titleLabel) { 324 | _titleLabel = [[UILabel alloc] init]; 325 | _titleLabel.numberOfLines = 0; 326 | _titleLabel.font = [UIFont systemFontOfSize:13]; 327 | } 328 | return _titleLabel; 329 | } 330 | 331 | - (UILabel *)detailLabel { 332 | if (!_detailLabel) { 333 | _detailLabel = [[UILabel alloc] init]; 334 | _detailLabel.numberOfLines = 0; 335 | _detailLabel.font = [UIFont systemFontOfSize:11]; 336 | _detailLabel.textColor = [UIColor lightGrayColor]; 337 | } 338 | return _detailLabel; 339 | } 340 | 341 | - (UILabel *)errorLabel { 342 | if (!_errorLabel) { 343 | _errorLabel = [[UILabel alloc] init]; 344 | _errorLabel.numberOfLines = 0; 345 | _errorLabel.font = [UIFont systemFontOfSize:15]; 346 | _errorLabel.textAlignment = NSTextAlignmentCenter; 347 | _errorLabel.userInteractionEnabled = YES; 348 | [_errorLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self 349 | action:@selector(errorClick:)]]; 350 | } 351 | return _errorLabel; 352 | } 353 | 354 | - (void)presentClick:(UIButton *)button { 355 | if (self.presentClick) { 356 | self.presentClick(); 357 | } 358 | } 359 | 360 | - (void)presentNaviClick:(UIButton *)button { 361 | if (self.presentNaviClick) { 362 | self.presentNaviClick(); 363 | } 364 | } 365 | 366 | - (void)errorClick:(UITapGestureRecognizer *)tap { 367 | if (self.model[kErrorKey]) { 368 | NSString *title = [NSString stringWithFormat:@"ErrorClass %@ - %@", self.model[kTitleKey], self.model[kNameKey]]; 369 | if (self.presentErrorClick) { 370 | self.presentErrorClick(title, self.model[kErrorKey]); 371 | } 372 | } 373 | } 374 | 375 | @end 376 | 377 | 378 | #pragma mark - VCPicker 379 | 380 | /** 381 | * floating view 悬浮球 382 | */ 383 | static VCPickerFloatingView *vcpicker_floatingView = nil; 384 | 385 | /** 386 | * class prefixes array 类名前缀 387 | */ 388 | static NSArray *vcpicker_prefixArray = nil; 389 | 390 | static NSArray *vcpicker_exceptArray = nil; 391 | 392 | /** 393 | * is VCPicker activated 是否已经激活 394 | */ 395 | static BOOL vcpicker_isActivated = NO; 396 | 397 | /** 398 | * all possible viewcontroller class info 所有可能的ViewController类型 399 | */ 400 | static NSArray *vcpicker_finalArray = nil; 401 | 402 | static BOOL vcpicker_needTitle = NO; 403 | 404 | static NSString *const vcpicker_searchHistoryKey = @"vcpicker.searchHistoryKey"; 405 | 406 | @interface VCPickerViewController () 407 | { 408 | /** 409 | * temp searched results 临时搜索到的数组 410 | */ 411 | NSArray *_tempArray; 412 | 413 | /** 414 | * history searched classes 历史搜索使用的数组 415 | */ 416 | NSMutableArray *_historyArray; 417 | } 418 | 419 | @property (nonatomic, strong) UITableView *tableView; 420 | @property (nonatomic, strong) UIButton *cancelButton; 421 | 422 | @end 423 | 424 | @implementation VCPickerViewController 425 | #pragma mark - Life cycle 426 | - (void)viewDidLoad { 427 | [super viewDidLoad]; 428 | 429 | self.view.backgroundColor = [UIColor whiteColor]; 430 | self.edgesForExtendedLayout = UIRectEdgeNone; 431 | 432 | UISearchBar *searchBar = [[UISearchBar alloc] init]; 433 | searchBar.placeholder = NSLocalizedString(@"Search", nil); 434 | searchBar.delegate = self; 435 | self.navigationItem.titleView = searchBar; 436 | 437 | [self loadHistoryData]; 438 | 439 | [self.view addSubview:self.cancelButton]; 440 | [self.view addSubview:self.tableView]; 441 | } 442 | 443 | // view appear to find all controllers 444 | - (void)viewDidAppear:(BOOL)animated { 445 | [super viewDidAppear:animated]; 446 | 447 | [self findAndShowControllers]; 448 | [VCPickerViewController setCircleHidden:YES]; 449 | } 450 | 451 | - (void)viewWillDisappear:(BOOL)animated { 452 | [super viewWillDisappear:animated]; 453 | 454 | UISearchBar *searchBar = (UISearchBar *)self.navigationItem.titleView; 455 | [searchBar resignFirstResponder]; 456 | 457 | [[self class] setCircleHidden:NO]; 458 | } 459 | 460 | //layout tableview 461 | - (void)viewWillLayoutSubviews { 462 | [super viewWillLayoutSubviews]; 463 | 464 | CGFloat buttonHeight = 40; 465 | CGFloat padding = 10; 466 | 467 | self.cancelButton.frame = CGRectMake(padding, 468 | CGRectGetHeight(self.view.bounds) - padding - buttonHeight, 469 | CGRectGetWidth(self.view.bounds) - 2*padding, 470 | buttonHeight); 471 | 472 | self.tableView.frame = CGRectMake(0, 473 | 0, 474 | CGRectGetWidth(self.view.bounds), 475 | CGRectGetMinY(self.cancelButton.frame) - padding); 476 | } 477 | 478 | /** 479 | * load history data 加载历史数据 480 | */ 481 | - (void)loadHistoryData { 482 | _historyArray = [[NSUserDefaults standardUserDefaults] objectForKey:vcpicker_searchHistoryKey]; 483 | if (_historyArray) { 484 | _historyArray = [_historyArray mutableCopy]; 485 | 486 | NSMutableArray *replaceArray = [NSMutableArray array]; 487 | NSMutableIndexSet *replaceSet = [NSMutableIndexSet indexSet]; 488 | 489 | NSMutableArray *copyArray = [NSMutableArray array]; 490 | NSMutableIndexSet *copySet = [NSMutableIndexSet indexSet]; 491 | for (NSDictionary *dic in _historyArray) { 492 | NSInteger index = [_historyArray indexOfObject:dic]; 493 | if ([dic isKindOfClass:[NSString class]]) { 494 | NSMutableDictionary *newDic = [NSMutableDictionary dictionary]; 495 | newDic[kTitleKey] = dic; 496 | newDic[kNameKey] = dic; 497 | 498 | [replaceSet addIndex:index]; 499 | [replaceArray addObject:newDic]; 500 | 501 | }else { 502 | NSMutableDictionary *newDic = [dic mutableCopy]; 503 | [copySet addIndex:index]; 504 | [copyArray addObject:newDic]; 505 | } 506 | } 507 | [_historyArray replaceObjectsAtIndexes:replaceSet withObjects:replaceArray]; 508 | [_historyArray replaceObjectsAtIndexes:copySet withObjects:copyArray]; 509 | 510 | } else { 511 | _historyArray = [NSMutableArray array]; 512 | } 513 | } 514 | 515 | /** 516 | * cancel pick 取消使用 517 | */ 518 | - (void)pickCancel { 519 | [self dismissViewControllerAnimated:YES completion:nil]; 520 | } 521 | 522 | /** 523 | * lazy initializing tableview 懒加载 524 | */ 525 | - (UITableView *)tableView { 526 | if (!_tableView) { 527 | _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; 528 | _tableView.delegate = self; 529 | _tableView.dataSource = self; 530 | } 531 | 532 | return _tableView; 533 | } 534 | 535 | - (UIButton *)cancelButton { 536 | if (!_cancelButton) { 537 | _cancelButton = [UIButton buttonWithType:UIButtonTypeCustom]; 538 | _cancelButton.backgroundColor = [UIColor colorWithRed:35/255.0 green:167/255.0 blue:67/255.0 alpha:1]; 539 | _cancelButton.layer.cornerRadius = 7.0f; 540 | 541 | [_cancelButton addTarget:self action:@selector(pickCancel) forControlEvents:UIControlEventTouchUpInside]; 542 | [_cancelButton setTitle:@"Cancel" forState:UIControlStateNormal]; 543 | [_cancelButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 544 | } 545 | 546 | return _cancelButton; 547 | } 548 | 549 | /** 550 | * find and show all viewcontroller in this project 551 | * \nNote: there are some UI classes can not be handled async 552 | * \nNote: some special classes can't conform to NSObject protocol, so avoid them 553 | * \nNote获取工程中所有的ViewController 554 | * \nNote注意,这里还是不要用异步处理了,系统有少量UI类异步处理会报线程错误 555 | * \nNote另外,部分特殊类不支持NSObject协议,需要手动剔除 556 | */ 557 | - (void)findAndShowControllers { 558 | if (!vcpicker_finalArray) { 559 | NSArray *classNameArray = [self findViewControllerClassNames]; 560 | 561 | NSMutableArray *array = [NSMutableArray array]; 562 | for (NSString *className in classNameArray) { 563 | UIViewController *controller = nil; 564 | NSMutableDictionary *dic = [NSMutableDictionary dictionary]; 565 | 566 | @try { 567 | if (vcpicker_needTitle) { 568 | controller = [self makeInstanceWithClass:NSClassFromString(className)]; // nil 569 | [controller loadViewIfNeeded]; // to active viewDidLoad so we can get conroller.title 570 | } 571 | 572 | } @catch (NSException *exception) { 573 | NSLog(@"[VCPicker <%@> exception: %@]", className, exception); 574 | dic[kErrorKey] = exception.description; 575 | 576 | } @finally { 577 | NSString *title; 578 | if (controller.title) { 579 | title = controller.title; 580 | } else if (controller.navigationItem.title) { 581 | title = controller.navigationItem.title; 582 | } else if (controller.tabBarItem.title) { 583 | title = controller.tabBarItem.title; 584 | } else { 585 | title = className; 586 | } 587 | 588 | dic[kNameKey] = className; 589 | dic[kTitleKey] = title; 590 | [self refreshHistoryForControllerInfo:dic]; 591 | [array addObject:dic]; 592 | } 593 | } 594 | 595 | vcpicker_finalArray = array; 596 | } 597 | 598 | _tempArray = vcpicker_finalArray; 599 | 600 | [self handleMissingHistory]; 601 | [self.tableView reloadData]; 602 | } 603 | 604 | - (UIViewController *)makeInstanceWithClass:(Class)clz { 605 | if ([(id)clz respondsToSelector:@selector(vcpicker_customViewController)]) { 606 | return [(id)clz vcpicker_customViewController]; 607 | } else { 608 | return [[clz alloc] init]; 609 | } 610 | } 611 | 612 | /** 613 | * 刷新历史数据信息 614 | * 615 | * @param classInfo 传入的类信息 616 | */ 617 | - (void)refreshHistoryForControllerInfo:(NSDictionary *)classInfo { 618 | for (NSMutableDictionary *dic in _historyArray) { 619 | if ([dic[kNameKey] isEqualToString:classInfo[kNameKey]]) { 620 | dic[kTitleKey] = classInfo[kTitleKey]; 621 | [self synchronizeHistory]; 622 | break; 623 | } 624 | } 625 | } 626 | 627 | /** 628 | * if some classes has gone remove it from history records 629 | * 如果有些类已经不存在,那么从历史记录中删除 630 | */ 631 | - (void)handleMissingHistory { 632 | NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet]; 633 | for (NSDictionary *dic in _historyArray) { 634 | BOOL isExist = NO; 635 | for (NSDictionary *finalDic in vcpicker_finalArray) { 636 | if ([dic[kNameKey] isEqualToString:finalDic[kNameKey]]) { 637 | isExist = YES; 638 | break; 639 | } 640 | } 641 | if (!isExist) { 642 | [indexSet addIndex:[_historyArray indexOfObject:dic]]; 643 | } 644 | } 645 | 646 | [_historyArray removeObjectsAtIndexes:indexSet]; 647 | } 648 | 649 | #pragma mark - UITableViewDelegate && UITableViewDataSource 650 | //history or search 651 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 652 | return 2; 653 | } 654 | 655 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 656 | return section ? _tempArray.count : _historyArray.count; 657 | } 658 | 659 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 660 | return section ? @"Search ↓" : @"History ↓"; 661 | } 662 | 663 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 664 | return 66; 665 | } 666 | 667 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 668 | static NSString *identifier = @"TestPickerCell"; 669 | VCPickerCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 670 | 671 | if (!cell) { 672 | cell = [[VCPickerCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; 673 | cell.textLabel.font = [UIFont systemFontOfSize:11]; 674 | } 675 | 676 | NSArray *dataArray = indexPath.section ? _tempArray : _historyArray; 677 | NSDictionary *classInfo = dataArray[indexPath.row]; 678 | [cell updateUIWithModel:classInfo]; 679 | 680 | __weak typeof(self) weakSelf = self; 681 | cell.presentClick = ^{ 682 | __strong typeof(self) self = weakSelf; 683 | [self saveAndShowController:classInfo showType:VCPickerShowTypePresent]; 684 | }; 685 | 686 | cell.presentNaviClick = ^{ 687 | __strong typeof(self) self = weakSelf; 688 | [self saveAndShowController:classInfo showType:VCPickerShowTypePresentNavi]; 689 | }; 690 | 691 | cell.presentErrorClick = ^(NSString *title, NSString *msg) { 692 | __strong typeof(self) self = weakSelf; 693 | UIAlertController *controller = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; 694 | [controller addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 695 | __strong typeof(self) self = weakSelf; 696 | [self dismissViewControllerAnimated:YES completion:nil]; 697 | }]]; 698 | [self presentViewController:controller animated:YES completion:nil]; 699 | }; 700 | 701 | return cell; 702 | } 703 | 704 | //edit history 705 | - (void)tableView:(UITableView *)tableView 706 | commitEditingStyle:(UITableViewCellEditingStyle)editingStyle 707 | forRowAtIndexPath:(NSIndexPath *)indexPath 708 | { 709 | [_historyArray removeObjectAtIndex:indexPath.row]; 710 | [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft]; 711 | 712 | [self synchronizeHistory]; 713 | } 714 | 715 | - (void)synchronizeHistory { 716 | [[NSUserDefaults standardUserDefaults] setObject:_historyArray forKey:vcpicker_searchHistoryKey]; 717 | } 718 | 719 | 720 | //can only edit history 721 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { 722 | return !indexPath.section; 723 | } 724 | 725 | // did select one ViewController and show it 726 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 727 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 728 | 729 | NSArray *dataArray = indexPath.section ? _tempArray : _historyArray; 730 | NSDictionary *classInfo = dataArray[indexPath.row]; 731 | 732 | [self saveAndShowController:classInfo showType:VCPickerShowTypePresentNavi]; 733 | } 734 | 735 | - (void)saveAndShowController:(NSDictionary *)controllerInfo showType:(VCPickerShowType)showType { 736 | [self addHistoryRecord:controllerInfo]; 737 | 738 | [self dismissViewControllerAnimated:YES completion:^{ 739 | NSString *controllerName = controllerInfo[kNameKey]; 740 | [self showViewController:controllerName showType:showType]; 741 | }]; 742 | } 743 | 744 | 745 | // hide the keyboard while scrolling 746 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 747 | UISearchBar *searchBar = (UISearchBar *)self.navigationItem.titleView; 748 | [searchBar resignFirstResponder]; 749 | } 750 | 751 | // add one history record, the same one will be avoied 752 | - (void)addHistoryRecord:(NSDictionary *)dic { 753 | if (dic[kErrorKey]) { 754 | return; 755 | } 756 | 757 | NSInteger index = NSNotFound; 758 | for (NSMutableDictionary *history in _historyArray) { 759 | if ([dic[kNameKey] isEqualToString:history[kNameKey]]) { 760 | index = [_historyArray indexOfObject:history]; 761 | } 762 | } 763 | 764 | if (index != NSNotFound) { 765 | [_historyArray removeObjectAtIndex:index]; 766 | } 767 | 768 | [_historyArray insertObject:dic.mutableCopy atIndex:0]; 769 | 770 | [self synchronizeHistory]; 771 | } 772 | 773 | #pragma mark - UISearchBarDelegate 774 | //find proper result while editing, ignore the upperCase of character 775 | - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { 776 | NSMutableArray *resultArray = [NSMutableArray array]; 777 | for (NSDictionary *classInfo in vcpicker_finalArray) { 778 | NSString *className = classInfo[kNameKey]; 779 | NSString *classTitle = classInfo[kTitleKey]; 780 | 781 | NSString *upperClassName = [className uppercaseString]; 782 | NSString *upperSearchText = [searchText uppercaseString]; 783 | 784 | NSRange rangeName = [upperClassName rangeOfString:upperSearchText]; 785 | NSRange rangeTitle = [classTitle rangeOfString:searchText]; 786 | 787 | BOOL isNameCompare = rangeName.location != NSNotFound; 788 | BOOL isTitleCompare = rangeTitle.location != NSNotFound; 789 | 790 | if (isNameCompare || isTitleCompare) { 791 | [resultArray addObject:classInfo]; 792 | } 793 | } 794 | 795 | _tempArray = searchText.length ? resultArray : vcpicker_finalArray; 796 | 797 | [self.tableView reloadData]; 798 | } 799 | 800 | //click search just to hide the keyboard 801 | - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { 802 | [searchBar resignFirstResponder]; 803 | } 804 | 805 | #pragma mark - Picker 806 | 807 | + (void)activateWhenDebug { 808 | [self activateWhenDebugWithClassPrefixes:nil except:nil needTitle:NO]; 809 | } 810 | 811 | + (void)activateWhenDebugWithClassPrefixes:(NSArray *)prefixes { 812 | [self activateWhenDebugWithClassPrefixes:prefixes except:nil needTitle:NO]; 813 | } 814 | 815 | + (void)activateWhenDebugWithClassPrefixes:(NSArray *)prefixes except:(NSArray *)exceptArray { 816 | [self activateWhenDebugWithClassPrefixes:prefixes except:exceptArray needTitle:NO]; 817 | } 818 | 819 | + (void)activateWhenDebugWithClassPrefixes:(NSArray *)prefixes 820 | except:(NSArray *)exceptArray 821 | needTitle:(BOOL)needTitle 822 | { 823 | vcpicker_isActivated = YES; 824 | vcpicker_needTitle = needTitle; 825 | 826 | [self showFinderWithClassPrefix:prefixes except:exceptArray]; 827 | } 828 | 829 | /** 830 | * 获取当前工程内所有带有特定前缀prefix的控制器,在Release模式下该方法失效 831 | * 832 | * @param prefixArray 前缀数组,比如 @[@"AB",@"ABC"],可为nil 833 | */ 834 | + (void)showFinderWithClassPrefix:(NSArray *)prefixArray except:(NSArray *)exceptArray { 835 | if (!vcpicker_isActivated) return; 836 | 837 | UIWindow *keyWindow = [self getMainWindow]; 838 | if (!keyWindow) return; 839 | 840 | if (!vcpicker_floatingView) { 841 | vcpicker_prefixArray = prefixArray; 842 | vcpicker_exceptArray = exceptArray; 843 | 844 | CGRect frame = CGRectMake(CGRectGetWidth(keyWindow.frame) - kFloatingLength, 150, kFloatingLength, kFloatingLength); 845 | vcpicker_floatingView = [[VCPickerFloatingView alloc] initWithFrame:frame]; 846 | vcpicker_floatingView.backgroundColor = [UIColor clearColor]; 847 | vcpicker_floatingView.floatingBlock = ^{ 848 | [VCPickerViewController setCircleHidden:YES]; 849 | [VCPickerViewController show]; 850 | }; 851 | } 852 | 853 | [keyWindow addSubview:vcpicker_floatingView]; 854 | } 855 | 856 | /** 857 | * show VC picker 显示选择器 858 | */ 859 | + (void)show { 860 | UIViewController *rootVC = [self getMainWindow].rootViewController; 861 | UIViewController *selfVC = [self new]; 862 | UINavigationController *naviedPickerVC = [[UINavigationController alloc] initWithRootViewController:selfVC]; 863 | naviedPickerVC.navigationBar.barStyle = UIBarStyleBlack; 864 | 865 | if (rootVC.presentedViewController) { 866 | [rootVC dismissViewControllerAnimated:YES completion:^{ 867 | [rootVC presentViewController:naviedPickerVC animated:YES completion:nil]; 868 | }]; 869 | } else { 870 | [rootVC presentViewController:naviedPickerVC animated:YES completion:nil]; 871 | } 872 | } 873 | 874 | - (void)dismissController { 875 | UIViewController *rootVC = [[self class] getMainWindow].rootViewController; 876 | [rootVC dismissViewControllerAnimated:YES completion:nil]; 877 | } 878 | 879 | /** 880 | * hide floatingView or not 设置是否隐藏悬浮球 881 | */ 882 | + (void)setCircleHidden:(BOOL)hidden { 883 | vcpicker_floatingView.hidden = hidden; 884 | } 885 | 886 | - (BOOL)willDealloc { 887 | return NO; 888 | } 889 | 890 | + (UIWindow *)getMainWindow { 891 | return [UIApplication sharedApplication].delegate.window; 892 | } 893 | 894 | /** 895 | * show some Controller 显示控制器页面 896 | * 897 | * @param showType 显示类型 898 | */ 899 | - (void)showViewController:(NSString *)controllerName showType:(VCPickerShowType)showType 900 | { 901 | Class clz = NSClassFromString(controllerName); 902 | if ([clz respondsToSelector:@selector(vcpicker_customShow)]) { 903 | [(id)clz vcpicker_customShow]; 904 | return; 905 | } 906 | 907 | UIViewController *controller = [self makeInstanceWithClass:clz]; 908 | 909 | switch (showType) { 910 | case VCPickerShowTypePush: { 911 | UIViewController *rootVC = [[self class] getMainWindow].rootViewController; 912 | 913 | if ([rootVC isKindOfClass:[UITabBarController class]]) { 914 | UITabBarController *tabbarVC = (UITabBarController *)rootVC; 915 | UINavigationController *naviVC = tabbarVC.selectedViewController; 916 | if ([naviVC isKindOfClass:[UINavigationController class]]) { 917 | [naviVC pushViewController:controller animated:YES]; 918 | }else { 919 | UINavigationController *aNaviVC = [[UINavigationController alloc] initWithRootViewController:controller]; 920 | [naviVC presentViewController:aNaviVC animated:YES completion:nil]; 921 | } 922 | 923 | }else if ([rootVC isKindOfClass:[UINavigationController class]]) { 924 | [((UINavigationController *)rootVC) pushViewController:controller animated:YES]; 925 | 926 | }else { 927 | UINavigationController *reulstNavi = [[UINavigationController alloc] initWithRootViewController:controller]; 928 | [rootVC presentViewController:reulstNavi animated:YES completion:nil]; 929 | } 930 | break; 931 | } 932 | 933 | case VCPickerShowTypePresent: { 934 | UIViewController *rootVC = [[self class] getMainWindow].rootViewController; 935 | [rootVC presentViewController:controller animated:YES completion:nil]; 936 | break; 937 | } 938 | 939 | case VCPickerShowTypePresentNavi: { 940 | UIViewController *rootVC = [[self class] getMainWindow].rootViewController; 941 | UIViewController *tobePresent = nil; 942 | if ([controller isKindOfClass:UINavigationController.class]) { 943 | tobePresent = controller; 944 | } else { 945 | tobePresent = [[UINavigationController alloc] initWithRootViewController:controller]; 946 | } 947 | [rootVC presentViewController:tobePresent animated:YES completion:nil]; 948 | 949 | break; 950 | } 951 | } 952 | } 953 | 954 | /** 955 | * 查找工程内的所有相关ViewController,浙江 956 | * find all viewcontrollers, this will block the main thread 957 | * 958 | * @return 控制器名字数组 959 | */ 960 | - (NSArray *)findViewControllerClassNames { 961 | Class *classes = NULL; 962 | int numClasses = objc_getClassList(NULL, 0); 963 | if (numClasses <= 0) return @[]; 964 | 965 | NSMutableArray *unSortedArray = [NSMutableArray array]; 966 | classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses); 967 | numClasses = objc_getClassList(classes, numClasses); 968 | for (int i = 0; i < numClasses; i++) { 969 | Class theClass = classes[i]; 970 | if (theClass == self.class) continue; 971 | 972 | NSString *className = [NSString stringWithUTF8String:class_getName(theClass)]; 973 | 974 | BOOL hasValidPrefix = false; 975 | if (vcpicker_prefixArray.count) { 976 | hasValidPrefix = [self judgeIsPreferredClass:className]; 977 | } else { 978 | if ([className hasPrefix:@"UI"]) continue; 979 | if ([className hasPrefix:@"_UI"]) continue; 980 | if ([className hasPrefix:@"NS"]) continue; 981 | if ([className hasPrefix:@"_NS"]) continue; 982 | if ([className hasPrefix:@"__"]) continue; 983 | if ([className hasPrefix:@"_"]) continue; 984 | if ([className hasPrefix:@"CMKApplication"]) continue; 985 | if ([className hasPrefix:@"CMKCamera"]) continue; 986 | if ([className hasPrefix:@"DeferredPU"]) continue; 987 | if ([className hasPrefix:@"AB"]) continue; // 通讯录 988 | if ([className hasPrefix:@"MK"]) continue; // 地图 989 | if ([className hasPrefix:@"MF"]) continue; // Messag 990 | if ([className hasPrefix:@"CN"]) continue; // Messag 991 | if ([className hasPrefix:@"SSDK"]) continue; // Messag 992 | if ([className hasPrefix:@"SSP"]) continue; // 993 | if ([className hasPrefix:@"QL"]) continue; // AIRPlay 994 | if ([className hasPrefix:@"GSAuto"]) continue; // GS AutoMap 995 | if ([self judgeIsSpecialClass:className]) continue; 996 | if ([self getRootClassOfClass:theClass] != NSObject.class) continue; 997 | 998 | hasValidPrefix = true; 999 | } 1000 | 1001 | if (!hasValidPrefix) continue; 1002 | if ([self judgeIsExceptClass:className]) continue; 1003 | if (![theClass isSubclassOfClass:[UIViewController class]]) continue; 1004 | 1005 | [unSortedArray addObject:className]; 1006 | } 1007 | free(classes); 1008 | 1009 | NSArray *sortedArray = [unSortedArray sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { 1010 | return [obj1 compare:obj2 options:NSForcedOrderingSearch]; 1011 | }]; 1012 | 1013 | return sortedArray; 1014 | } 1015 | 1016 | 1017 | /** 1018 | * 判断是否为特殊的系统类 1019 | * 1020 | * @param className 传入类名进行判断 1021 | * 1022 | * @return YES为特殊类,NO不是 1023 | */ 1024 | - (BOOL)judgeIsSpecialClass:(NSString *)className { 1025 | for (NSString *aClass in [self specialClassArray]) { 1026 | if ([className isEqualToString:aClass]) { 1027 | return YES; 1028 | } 1029 | } 1030 | 1031 | return NO; 1032 | } 1033 | 1034 | - (BOOL)judgeIsPreferredClass:(NSString *)className { 1035 | for (NSString *prefix in vcpicker_prefixArray) { 1036 | if ([className hasPrefix:prefix]) { 1037 | return YES; 1038 | } 1039 | } 1040 | 1041 | return NO; 1042 | } 1043 | 1044 | - (BOOL)judgeIsExceptClass:(NSString *)className { 1045 | for (NSString *except in vcpicker_exceptArray) { 1046 | if ([className containsString:except]) { 1047 | return YES; 1048 | } 1049 | } 1050 | 1051 | return NO; 1052 | } 1053 | 1054 | 1055 | /** 1056 | * 一些特殊的系统类,不支持NSObject协议,需要手动剔除 1057 | * 1058 | */ 1059 | - (NSArray *)specialClassArray { 1060 | static NSArray *special; 1061 | static dispatch_once_t onceToken; 1062 | dispatch_once(&onceToken, ^{ 1063 | special = @[@"JSExport", @"__NSMessageBuilder", @"Object", @"__ARCLite__", @"__NSAtom", 1064 | @"__NSGenericDeallocHandler", @"_NSZombie_", @"CLTilesManagerClient", 1065 | @"FigIrisAutoTrimmerMotionSampleExport", @"CNZombie", @"_CNZombie_", 1066 | @"ABContactViewController", @"ABLabelPickerViewController", 1067 | @"ABStarkContactViewController", @"CNContactContentViewController", 1068 | @"CNContactViewServiceViewController", @"CNStarkContactViewController", 1069 | @"MKActivityViewController", @"MKPlaceInfoViewController", 1070 | @"CNUI", @"UISearchController", @"WKObject"]; // UI的类在子线程访问有问题 1071 | }); 1072 | 1073 | return special; 1074 | } 1075 | 1076 | /** 1077 | * 获取类的根类 1078 | * 1079 | * @param aClass 传入一个类 1080 | * 1081 | * @return 获取根类 1082 | */ 1083 | - (Class)getRootClassOfClass:(Class)aClass { 1084 | if (!aClass) 1085 | return nil; 1086 | 1087 | Class superClass = nil; 1088 | if ([aClass respondsToSelector:@selector(superclass)]) { 1089 | superClass = aClass.superclass; 1090 | } 1091 | 1092 | if (!superClass) 1093 | return aClass; 1094 | 1095 | return [self getRootClassOfClass:superClass]; 1096 | } 1097 | 1098 | @end 1099 | 1100 | #pragma mark - swizzle 1101 | 1102 | @interface UIWindow (swizzle) 1103 | @end 1104 | @implementation UIWindow (swizzle) 1105 | 1106 | + (void)load { 1107 | [self swizzleSel:@selector(makeKeyAndVisible) withSel:@selector(swizzle_makeKeyAndVisiable)]; 1108 | [self swizzleSel:@selector(setRootViewController:) withSel:@selector(swizzle_setRootViewController:)]; 1109 | } 1110 | 1111 | + (void)swizzleSel:(SEL)sel withSel:(SEL)swizzleSel { 1112 | Method fromMethod = class_getInstanceMethod(self, sel); 1113 | Method toMethod = class_getInstanceMethod(self, swizzleSel); 1114 | method_exchangeImplementations(fromMethod, toMethod); 1115 | } 1116 | 1117 | - (void)swizzle_makeKeyAndVisiable { 1118 | [self swizzle_makeKeyAndVisiable]; 1119 | 1120 | if (self == [VCPickerViewController getMainWindow]) { 1121 | [VCPickerViewController showFinderWithClassPrefix:vcpicker_exceptArray except:vcpicker_exceptArray]; 1122 | } 1123 | } 1124 | 1125 | - (void)swizzle_setRootViewController:(UIViewController *)rootViewController { 1126 | [self swizzle_setRootViewController:rootViewController]; 1127 | 1128 | if (self == [VCPickerViewController getMainWindow]) { 1129 | [VCPickerViewController showFinderWithClassPrefix:vcpicker_exceptArray except:vcpicker_exceptArray]; 1130 | } 1131 | } 1132 | 1133 | @end 1134 | 1135 | #endif 1136 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 94C2846528B693AD00DA9C4E /* VCPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C2846428B693AD00DA9C4E /* VCPickerViewController.m */; }; 11 | BCDB7B7C1DD41F82000E4030 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = BCDB7B7B1DD41F82000E4030 /* main.m */; }; 12 | BCDB7B7F1DD41F82000E4030 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = BCDB7B7E1DD41F82000E4030 /* AppDelegate.m */; }; 13 | BCDB7B821DD41F82000E4030 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BCDB7B811DD41F82000E4030 /* ViewController.m */; }; 14 | BCDB7B851DD41F82000E4030 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BCDB7B831DD41F82000E4030 /* Main.storyboard */; }; 15 | BCDB7B871DD41F83000E4030 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BCDB7B861DD41F83000E4030 /* Assets.xcassets */; }; 16 | BCDB7B8A1DD41F83000E4030 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BCDB7B881DD41F83000E4030 /* LaunchScreen.storyboard */; }; 17 | BCDB7B961DD42712000E4030 /* MYViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BCDB7B951DD42712000E4030 /* MYViewController.m */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 94C2846328B693AD00DA9C4E /* VCPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VCPickerViewController.h; sourceTree = ""; }; 22 | 94C2846428B693AD00DA9C4E /* VCPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VCPickerViewController.m; sourceTree = ""; }; 23 | 94C2846628B6943300DA9C4E /* VCPicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VCPicker.h; sourceTree = ""; }; 24 | BCDB7B771DD41F82000E4030 /* VCPickerDev.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VCPickerDev.app; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | BCDB7B7B1DD41F82000E4030 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 26 | BCDB7B7D1DD41F82000E4030 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 27 | BCDB7B7E1DD41F82000E4030 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 28 | BCDB7B801DD41F82000E4030 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 29 | BCDB7B811DD41F82000E4030 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 30 | BCDB7B841DD41F82000E4030 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 31 | BCDB7B861DD41F83000E4030 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 32 | BCDB7B891DD41F83000E4030 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 33 | BCDB7B8B1DD41F83000E4030 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | BCDB7B941DD42712000E4030 /* MYViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYViewController.h; sourceTree = ""; }; 35 | BCDB7B951DD42712000E4030 /* MYViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYViewController.m; sourceTree = ""; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | BCDB7B741DD41F82000E4030 /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | 94C2846128B693AD00DA9C4E /* VCPicker */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 94C2846628B6943300DA9C4E /* VCPicker.h */, 53 | 94C2846228B693AD00DA9C4E /* sources */, 54 | ); 55 | name = VCPicker; 56 | path = ../VCPicker; 57 | sourceTree = ""; 58 | }; 59 | 94C2846228B693AD00DA9C4E /* sources */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 94C2846328B693AD00DA9C4E /* VCPickerViewController.h */, 63 | 94C2846428B693AD00DA9C4E /* VCPickerViewController.m */, 64 | ); 65 | path = sources; 66 | sourceTree = ""; 67 | }; 68 | BCDB7B6E1DD41F82000E4030 = { 69 | isa = PBXGroup; 70 | children = ( 71 | 94C2846128B693AD00DA9C4E /* VCPicker */, 72 | BCDB7B791DD41F82000E4030 /* VCPickerDev */, 73 | BCDB7B781DD41F82000E4030 /* Products */, 74 | ); 75 | sourceTree = ""; 76 | }; 77 | BCDB7B781DD41F82000E4030 /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | BCDB7B771DD41F82000E4030 /* VCPickerDev.app */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | BCDB7B791DD41F82000E4030 /* VCPickerDev */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | BCDB7B7D1DD41F82000E4030 /* AppDelegate.h */, 89 | BCDB7B7E1DD41F82000E4030 /* AppDelegate.m */, 90 | BCDB7B801DD41F82000E4030 /* ViewController.h */, 91 | BCDB7B811DD41F82000E4030 /* ViewController.m */, 92 | BCDB7B941DD42712000E4030 /* MYViewController.h */, 93 | BCDB7B951DD42712000E4030 /* MYViewController.m */, 94 | BCDB7B831DD41F82000E4030 /* Main.storyboard */, 95 | BCDB7B861DD41F83000E4030 /* Assets.xcassets */, 96 | BCDB7B881DD41F83000E4030 /* LaunchScreen.storyboard */, 97 | BCDB7B8B1DD41F83000E4030 /* Info.plist */, 98 | BCDB7B7A1DD41F82000E4030 /* Supporting Files */, 99 | ); 100 | path = VCPickerDev; 101 | sourceTree = ""; 102 | }; 103 | BCDB7B7A1DD41F82000E4030 /* Supporting Files */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | BCDB7B7B1DD41F82000E4030 /* main.m */, 107 | ); 108 | name = "Supporting Files"; 109 | sourceTree = ""; 110 | }; 111 | /* End PBXGroup section */ 112 | 113 | /* Begin PBXNativeTarget section */ 114 | BCDB7B761DD41F82000E4030 /* VCPickerDev */ = { 115 | isa = PBXNativeTarget; 116 | buildConfigurationList = BCDB7B8E1DD41F83000E4030 /* Build configuration list for PBXNativeTarget "VCPickerDev" */; 117 | buildPhases = ( 118 | BCDB7B731DD41F82000E4030 /* Sources */, 119 | BCDB7B741DD41F82000E4030 /* Frameworks */, 120 | BCDB7B751DD41F82000E4030 /* Resources */, 121 | ); 122 | buildRules = ( 123 | ); 124 | dependencies = ( 125 | ); 126 | name = VCPickerDev; 127 | productName = TestVCPicker2; 128 | productReference = BCDB7B771DD41F82000E4030 /* VCPickerDev.app */; 129 | productType = "com.apple.product-type.application"; 130 | }; 131 | /* End PBXNativeTarget section */ 132 | 133 | /* Begin PBXProject section */ 134 | BCDB7B6F1DD41F82000E4030 /* Project object */ = { 135 | isa = PBXProject; 136 | attributes = { 137 | CLASSPREFIX = VCPicker; 138 | LastUpgradeCheck = 1340; 139 | ORGANIZATIONNAME = beforeold; 140 | TargetAttributes = { 141 | BCDB7B761DD41F82000E4030 = { 142 | CreatedOnToolsVersion = 8.1; 143 | DevelopmentTeam = WS8S43M437; 144 | ProvisioningStyle = Automatic; 145 | }; 146 | }; 147 | }; 148 | buildConfigurationList = BCDB7B721DD41F82000E4030 /* Build configuration list for PBXProject "VCPickerDev" */; 149 | compatibilityVersion = "Xcode 3.2"; 150 | developmentRegion = en; 151 | hasScannedForEncodings = 0; 152 | knownRegions = ( 153 | en, 154 | Base, 155 | ); 156 | mainGroup = BCDB7B6E1DD41F82000E4030; 157 | productRefGroup = BCDB7B781DD41F82000E4030 /* Products */; 158 | projectDirPath = ""; 159 | projectRoot = ""; 160 | targets = ( 161 | BCDB7B761DD41F82000E4030 /* VCPickerDev */, 162 | ); 163 | }; 164 | /* End PBXProject section */ 165 | 166 | /* Begin PBXResourcesBuildPhase section */ 167 | BCDB7B751DD41F82000E4030 /* Resources */ = { 168 | isa = PBXResourcesBuildPhase; 169 | buildActionMask = 2147483647; 170 | files = ( 171 | BCDB7B8A1DD41F83000E4030 /* LaunchScreen.storyboard in Resources */, 172 | BCDB7B871DD41F83000E4030 /* Assets.xcassets in Resources */, 173 | BCDB7B851DD41F82000E4030 /* Main.storyboard in Resources */, 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | /* End PBXResourcesBuildPhase section */ 178 | 179 | /* Begin PBXSourcesBuildPhase section */ 180 | BCDB7B731DD41F82000E4030 /* Sources */ = { 181 | isa = PBXSourcesBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | 94C2846528B693AD00DA9C4E /* VCPickerViewController.m in Sources */, 185 | BCDB7B821DD41F82000E4030 /* ViewController.m in Sources */, 186 | BCDB7B7F1DD41F82000E4030 /* AppDelegate.m in Sources */, 187 | BCDB7B7C1DD41F82000E4030 /* main.m in Sources */, 188 | BCDB7B961DD42712000E4030 /* MYViewController.m in Sources */, 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | }; 192 | /* End PBXSourcesBuildPhase section */ 193 | 194 | /* Begin PBXVariantGroup section */ 195 | BCDB7B831DD41F82000E4030 /* Main.storyboard */ = { 196 | isa = PBXVariantGroup; 197 | children = ( 198 | BCDB7B841DD41F82000E4030 /* Base */, 199 | ); 200 | name = Main.storyboard; 201 | sourceTree = ""; 202 | }; 203 | BCDB7B881DD41F83000E4030 /* LaunchScreen.storyboard */ = { 204 | isa = PBXVariantGroup; 205 | children = ( 206 | BCDB7B891DD41F83000E4030 /* Base */, 207 | ); 208 | name = LaunchScreen.storyboard; 209 | sourceTree = ""; 210 | }; 211 | /* End PBXVariantGroup section */ 212 | 213 | /* Begin XCBuildConfiguration section */ 214 | BCDB7B8C1DD41F83000E4030 /* Debug */ = { 215 | isa = XCBuildConfiguration; 216 | buildSettings = { 217 | ALWAYS_SEARCH_USER_PATHS = NO; 218 | CLANG_ANALYZER_NONNULL = YES; 219 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 220 | CLANG_CXX_LIBRARY = "libc++"; 221 | CLANG_ENABLE_MODULES = YES; 222 | CLANG_ENABLE_OBJC_ARC = YES; 223 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 224 | CLANG_WARN_BOOL_CONVERSION = YES; 225 | CLANG_WARN_COMMA = YES; 226 | CLANG_WARN_CONSTANT_CONVERSION = YES; 227 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 228 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 229 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 230 | CLANG_WARN_EMPTY_BODY = YES; 231 | CLANG_WARN_ENUM_CONVERSION = YES; 232 | CLANG_WARN_INFINITE_RECURSION = YES; 233 | CLANG_WARN_INT_CONVERSION = YES; 234 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 235 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 236 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 237 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 238 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 239 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 240 | CLANG_WARN_STRICT_PROTOTYPES = YES; 241 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 242 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 243 | CLANG_WARN_UNREACHABLE_CODE = YES; 244 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 245 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 246 | COPY_PHASE_STRIP = NO; 247 | DEBUG_INFORMATION_FORMAT = dwarf; 248 | ENABLE_STRICT_OBJC_MSGSEND = YES; 249 | ENABLE_TESTABILITY = YES; 250 | GCC_C_LANGUAGE_STANDARD = gnu99; 251 | GCC_DYNAMIC_NO_PIC = NO; 252 | GCC_NO_COMMON_BLOCKS = YES; 253 | GCC_OPTIMIZATION_LEVEL = 0; 254 | GCC_PREPROCESSOR_DEFINITIONS = ( 255 | "DEBUG=1", 256 | "$(inherited)", 257 | ); 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 265 | MTL_ENABLE_DEBUG_INFO = YES; 266 | ONLY_ACTIVE_ARCH = YES; 267 | SDKROOT = iphoneos; 268 | }; 269 | name = Debug; 270 | }; 271 | BCDB7B8D1DD41F83000E4030 /* Release */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ALWAYS_SEARCH_USER_PATHS = NO; 275 | CLANG_ANALYZER_NONNULL = YES; 276 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 277 | CLANG_CXX_LIBRARY = "libc++"; 278 | CLANG_ENABLE_MODULES = YES; 279 | CLANG_ENABLE_OBJC_ARC = YES; 280 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 281 | CLANG_WARN_BOOL_CONVERSION = YES; 282 | CLANG_WARN_COMMA = YES; 283 | CLANG_WARN_CONSTANT_CONVERSION = YES; 284 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 285 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 286 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 287 | CLANG_WARN_EMPTY_BODY = YES; 288 | CLANG_WARN_ENUM_CONVERSION = YES; 289 | CLANG_WARN_INFINITE_RECURSION = YES; 290 | CLANG_WARN_INT_CONVERSION = YES; 291 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 292 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 293 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 295 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 296 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 297 | CLANG_WARN_STRICT_PROTOTYPES = YES; 298 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 299 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 300 | CLANG_WARN_UNREACHABLE_CODE = YES; 301 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 302 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 303 | COPY_PHASE_STRIP = NO; 304 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 305 | ENABLE_NS_ASSERTIONS = NO; 306 | ENABLE_STRICT_OBJC_MSGSEND = YES; 307 | GCC_C_LANGUAGE_STANDARD = gnu99; 308 | GCC_NO_COMMON_BLOCKS = YES; 309 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 310 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 311 | GCC_WARN_UNDECLARED_SELECTOR = YES; 312 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 313 | GCC_WARN_UNUSED_FUNCTION = YES; 314 | GCC_WARN_UNUSED_VARIABLE = YES; 315 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 316 | MTL_ENABLE_DEBUG_INFO = NO; 317 | SDKROOT = iphoneos; 318 | VALIDATE_PRODUCT = YES; 319 | }; 320 | name = Release; 321 | }; 322 | BCDB7B8F1DD41F83000E4030 /* Debug */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 326 | DEVELOPMENT_TEAM = WS8S43M437; 327 | INFOPLIST_FILE = VCPickerDev/Info.plist; 328 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 329 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 330 | MARKETING_VERSION = 2.1; 331 | PRODUCT_BUNDLE_IDENTIFIER = com.br.vcpicker.example; 332 | PRODUCT_NAME = "$(TARGET_NAME)"; 333 | }; 334 | name = Debug; 335 | }; 336 | BCDB7B901DD41F83000E4030 /* Release */ = { 337 | isa = XCBuildConfiguration; 338 | buildSettings = { 339 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 340 | DEVELOPMENT_TEAM = WS8S43M437; 341 | INFOPLIST_FILE = VCPickerDev/Info.plist; 342 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 343 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 344 | MARKETING_VERSION = 2.1; 345 | PRODUCT_BUNDLE_IDENTIFIER = com.br.vcpicker.example; 346 | PRODUCT_NAME = "$(TARGET_NAME)"; 347 | }; 348 | name = Release; 349 | }; 350 | /* End XCBuildConfiguration section */ 351 | 352 | /* Begin XCConfigurationList section */ 353 | BCDB7B721DD41F82000E4030 /* Build configuration list for PBXProject "VCPickerDev" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | BCDB7B8C1DD41F83000E4030 /* Debug */, 357 | BCDB7B8D1DD41F83000E4030 /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | BCDB7B8E1DD41F83000E4030 /* Build configuration list for PBXNativeTarget "VCPickerDev" */ = { 363 | isa = XCConfigurationList; 364 | buildConfigurations = ( 365 | BCDB7B8F1DD41F83000E4030 /* Debug */, 366 | BCDB7B901DD41F83000E4030 /* Release */, 367 | ); 368 | defaultConfigurationIsVisible = 0; 369 | defaultConfigurationName = Release; 370 | }; 371 | /* End XCConfigurationList section */ 372 | }; 373 | rootObject = BCDB7B6F1DD41F82000E4030 /* Project object */; 374 | } 375 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TestVCPicker2 4 | // 5 | // Created by beforeold on 2016/11/10. 6 | // Copyright © 2016年 Brook. 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 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TestVCPicker2 4 | // 5 | // Created by beforeold on 2016/11/10. 6 | // Copyright © 2016年 Brook. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "VCPickerViewController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | 22 | // it's recommended to pass a class prefix array to ignore unused classes 23 | [VCPickerViewController activateWhenDebugWithClassPrefixes:@[@"MY"]]; 24 | 25 | return YES; 26 | } 27 | 28 | 29 | - (void)applicationWillResignActive:(UIApplication *)application { 30 | // 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. 31 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 32 | } 33 | 34 | 35 | - (void)applicationDidEnterBackground:(UIApplication *)application { 36 | // 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. 37 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 38 | } 39 | 40 | 41 | - (void)applicationWillEnterForeground:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationDidBecomeActive:(UIApplication *)application { 47 | // 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. 48 | } 49 | 50 | 51 | - (void)applicationWillTerminate:(UIApplication *)application { 52 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 53 | } 54 | 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev/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 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev/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 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | $(MARKETING_VERSION) 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 | 38 | 39 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev/MYViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYViewController.h 3 | // TestVCPicker2 4 | // 5 | // Created by beforeold on 2016/11/10. 6 | // Copyright © 2016年 Brook. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MYViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev/MYViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYViewController.m 3 | // TestVCPicker2 4 | // 5 | // Created by beforeold on 2016/11/10. 6 | // Copyright © 2016年 Brook. All rights reserved. 7 | // 8 | 9 | #import "MYViewController.h" 10 | 11 | @interface MYViewController () 12 | 13 | @end 14 | 15 | @implementation MYViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | 20 | 21 | self.view.backgroundColor = [UIColor lightGrayColor]; 22 | self.title = @"Profile"; 23 | } 24 | 25 | - (void)didReceiveMemoryWarning { 26 | [super didReceiveMemoryWarning]; 27 | // Dispose of any resources that can be recreated. 28 | } 29 | 30 | /* 31 | #pragma mark - Navigation 32 | 33 | // In a storyboard-based application, you will often want to do a little preparation before navigation 34 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 35 | // Get the new view controller using [segue destinationViewController]. 36 | // Pass the selected object to the new view controller. 37 | } 38 | */ 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TestVCPicker2 4 | // 5 | // Created by beforeold on 2016/11/10. 6 | // 7 | 8 | #import 9 | 10 | @interface ViewController : UIViewController 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // TestVCPicker2 4 | // 5 | // Created by beforeold on 2016/11/10. 6 | // 7 | 8 | #import "ViewController.h" 9 | 10 | @interface ViewController () 11 | 12 | @end 13 | 14 | @implementation ViewController 15 | 16 | - (void)viewDidLoad { 17 | [super viewDidLoad]; 18 | 19 | 20 | self.title = @"Test"; 21 | self.view.backgroundColor = [UIColor cyanColor]; 22 | } 23 | 24 | 25 | - (void)didReceiveMemoryWarning { 26 | [super didReceiveMemoryWarning]; 27 | // Dispose of any resources that can be recreated. 28 | } 29 | 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /VCPickerDev/VCPickerDev/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TestVCPicker2 4 | // 5 | // Created by beforeold on 2016/11/10. 6 | // Copyright © 2016年 Brook. 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 | --------------------------------------------------------------------------------