├── .gitignore ├── Example ├── Podfile ├── Podfile.lock ├── RRUIViewControllerDemo.xcodeproj │ ├── RRUIViewControllerExtension │ │ ├── UINavigationController+RRSet.h │ │ ├── UINavigationController+RRSet.m │ │ ├── UIViewController+GlobalConfig.h │ │ ├── UIViewController+GlobalConfig.m │ │ └── resources │ │ │ └── icon_button_return@2x.png │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── RRUIViewControllerDemo.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── RRUIViewControllerDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── TintBlue.colorset │ │ └── Contents.json │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── ViewControllers │ ├── DEMO_HiddenNaviBarViewController.h │ ├── DEMO_HiddenNaviBarViewController.m │ ├── DEMO_ImageNaviBarViewController.h │ ├── DEMO_ImageNaviBarViewController.m │ ├── DEMO_TransparentNaviBarViewController.h │ ├── DEMO_TransparentNaviBarViewController.m │ ├── DEMO_normalMemoryLeakViewController.h │ ├── DEMO_normalMemoryLeakViewController.m │ ├── DEMO_normalMemoryLeakViewController.xib │ ├── DynamicConfig │ │ ├── DynamicConfigViewController.h │ │ ├── DynamicConfigViewController.m │ │ └── DynamicConfigViewController.xib │ ├── StatisticsViewController.h │ ├── StatisticsViewController.m │ ├── SwitchableViewController.h │ ├── SwitchableViewController.m │ └── commView │ │ ├── CommView.h │ │ ├── CommView.m │ │ └── CommView.xib │ ├── ar.lproj │ └── LaunchScreen.strings │ ├── cusNavigationBar@2x.png │ ├── icon_apple@2x.png │ └── main.m ├── LICENSE ├── README.md ├── RRViewControllerExtension.podspec └── RRViewControllerExtension ├── Pod └── README.md ├── RRViewControllerExtension.h ├── UINavigationController+RRSet.h ├── UINavigationController+RRSet.m ├── UIViewController+RRExtension.h ├── UIViewController+RRExtension.m ├── UIViewController+RRStatistics.h └── UIViewController+RRStatistics.m /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | *.pbxuser 4 | *.perspective 5 | *.perspectivev3 6 | 7 | *.mode1v3 8 | *.mode2v3 9 | 10 | *.xcuserstate 11 | 12 | *.xcodeproj/xcuserdata/*.xcuserdatad 13 | 14 | *.xccheckout 15 | *.xcuserdatad 16 | 17 | Pods 18 | 19 | DerivedData 20 | build 21 | 22 | *.swp 23 | 24 | *.gcov 25 | *.gcno 26 | *.gcda 27 | 28 | ## Playgrounds 29 | timeline.xctimeline 30 | playground.xcworkspace 31 | 32 | # Buck 33 | /buck-out 34 | /.buckconfig.local 35 | /.buckd 36 | 37 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'RRUIViewControllerDemo' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for RRUIViewControllerDemo 9 | 10 | pod 'RRViewControllerExtension', :path => '../' 11 | 12 | end 13 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - RRViewControllerExtension (3.2.0) 3 | 4 | DEPENDENCIES: 5 | - RRViewControllerExtension (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | RRViewControllerExtension: 9 | :path: "../" 10 | 11 | SPEC CHECKSUMS: 12 | RRViewControllerExtension: fe7d20a8289bb02f1715778fbebda7e3313f9636 13 | 14 | PODFILE CHECKSUM: 8f554c242292946d4fca7f60dcb8c4230a4f8777 15 | 16 | COCOAPODS: 1.15.2 17 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo.xcodeproj/RRUIViewControllerExtension/UINavigationController+RRSet.h: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController+RRSet.h 3 | // Pods-RRUIViewControllerExtention_Example 4 | // 5 | // Created by 罗亮富 on 2018/9/17. 6 | // 7 | 8 | /* 9 | navigationBarTrasnparent -> navigationBarTransparent 10 | */ 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface UINavigationController (RRSet) 17 | 18 | @property (nonatomic) BOOL applyGlobalConfig; 19 | @property (nonatomic, getter=isNavigationBarTransparent) BOOL navigationBarTransparent; 20 | 21 | -(instancetype)initWithRootViewController:(UIViewController *)rootViewController applyGlobalConfig:(BOOL)apply; 22 | 23 | 24 | // pop/push with completion block call backs 25 | - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated completionBlock:(void (^ __nullable)(void))completion; 26 | - (nullable UIViewController *)popViewControllerAnimated:(BOOL)animated completionBlock:(void (^ __nullable)(void))completion; 27 | - (nullable NSArray<__kindof UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated completionBlock:(void (^ __nullable)(void))completion; 28 | - (nullable NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated completionBlock:(void (^ __nullable)(void))completion; 29 | 30 | @end 31 | 32 | 33 | 34 | @interface UINavigationItem (StatusStack) 35 | //恢复之前push的NavigationItem 36 | -(void)popStatus; 37 | //保存NavigationItem 38 | -(void)pushStatus; 39 | 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo.xcodeproj/RRUIViewControllerExtension/UINavigationController+RRSet.m: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController+RRSet.m 3 | // Pods-RRUIViewControllerExtention_Example 4 | // 5 | // Created by 罗亮富 on 2018/9/17. 6 | // 7 | 8 | #import "UINavigationController+RRSet.h" 9 | #import 10 | 11 | #define kNavigationCompletionBlockKey @"completionBlk" 12 | #define kNavigationControllerApplyGlobalConfigKey @"globalCfig" 13 | static UIImage *sNavigationBarTransparentImage; 14 | 15 | @implementation UINavigationController (RRSet) 16 | 17 | +(void)initialize 18 | { 19 | static dispatch_once_t onceToken; 20 | dispatch_once(&onceToken, ^{ 21 | Class class = [self class]; 22 | 23 | #pragma clang diagnostic push 24 | #pragma clang diagnostic ignored "-Wundeclared-selector" 25 | SEL originalSelector = @selector(navigationTransitionView:didEndTransition:fromView:toView:); 26 | SEL swizzledSelector = @selector(mob_navigationTransitionView:didEndTransition:fromView:toView:); 27 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class, swizzledSelector)); 28 | #pragma clang diagnostic pop 29 | 30 | }); 31 | } 32 | 33 | -(instancetype)initWithRootViewController:(UIViewController *)rootViewController applyGlobalConfig:(BOOL)apply 34 | { 35 | UINavigationController *nav = [self initWithRootViewController:rootViewController]; 36 | nav.applyGlobalConfig = apply; 37 | return nav; 38 | } 39 | 40 | #pragma mark- transparent 41 | 42 | -(void)setNavigationBarTransparent:(BOOL)transparent 43 | { 44 | if(transparent == self.navigationBarTransparent) 45 | return; 46 | 47 | UIImage *img = nil; 48 | 49 | if(transparent) 50 | { 51 | if(!sNavigationBarTransparentImage) 52 | { 53 | CGRect rect = CGRectMake(0, 0, 1, 1); 54 | 55 | UIGraphicsBeginImageContext(rect.size); 56 | 57 | CGContextRef context = UIGraphicsGetCurrentContext(); 58 | CGContextSetFillColorWithColor(context,[UIColor clearColor].CGColor); 59 | CGContextFillRect(context, rect); 60 | sNavigationBarTransparentImage = UIGraphicsGetImageFromCurrentImageContext(); 61 | UIGraphicsEndImageContext(); 62 | } 63 | img = sNavigationBarTransparentImage; 64 | } 65 | 66 | [self.navigationBar setBackgroundImage:img forBarMetrics:UIBarMetricsDefault]; 67 | [self.navigationBar setShadowImage:img]; 68 | 69 | } 70 | 71 | -(BOOL)isNavigationBarTransparent 72 | { 73 | #warning 这种方式不准确 74 | UIImage *bgImage = [self.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault]; 75 | return [bgImage isEqual:sNavigationBarTransparentImage]; 76 | } 77 | 78 | #warning removed 79 | //- (void)mob_pushViewController:(UIViewController *)viewController animated:(BOOL)animated 80 | //{ 81 | // if( self.viewControllers.count > 0) 82 | // viewController.hidesBottomBarWhenPushed = YES; 83 | // 84 | // [self mob_pushViewController:viewController animated:animated]; 85 | //} 86 | 87 | #pragma mark- push/pop completion block 88 | 89 | -(void)setCompletionBlock:(void (^ __nullable)(void))completion 90 | { 91 | objc_setAssociatedObject(self, kNavigationCompletionBlockKey, completion, OBJC_ASSOCIATION_COPY_NONATOMIC); 92 | } 93 | -(void)mob_navigationTransitionView:(id)obj1 didEndTransition:(BOOL)b fromView:(id)v1 toView:(id)v2 94 | { 95 | [self mob_navigationTransitionView:obj1 didEndTransition:b fromView:v1 toView:v2]; 96 | void (^ cmpltBlock)(void) = objc_getAssociatedObject(self, kNavigationCompletionBlockKey); 97 | if(cmpltBlock) 98 | cmpltBlock(); 99 | 100 | [self setCompletionBlock:nil]; 101 | } 102 | 103 | -(void)setApplyGlobalConfig:(BOOL)applyGlobalConfig 104 | { 105 | objc_setAssociatedObject(self, kNavigationControllerApplyGlobalConfigKey, [NSNumber numberWithBool:applyGlobalConfig], OBJC_ASSOCIATION_COPY_NONATOMIC); 106 | } 107 | 108 | -(BOOL)applyGlobalConfig 109 | { 110 | NSNumber *boolNum = objc_getAssociatedObject(self, kNavigationControllerApplyGlobalConfigKey); 111 | return boolNum.boolValue; 112 | } 113 | 114 | - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated completionBlock:(void (^ __nullable)(void))completion 115 | { 116 | [self setCompletionBlock:completion]; 117 | [self pushViewController:viewController animated:animated]; 118 | } 119 | 120 | - (nullable UIViewController *)popViewControllerAnimated:(BOOL)animated completionBlock:(void (^ __nullable)(void))completion 121 | { 122 | [self setCompletionBlock:completion]; 123 | return [self popViewControllerAnimated:animated]; 124 | } 125 | 126 | - (nullable NSArray<__kindof UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated completionBlock:(void (^ __nullable)(void))completion 127 | { 128 | [self setCompletionBlock:completion]; 129 | return [self popToViewController:viewController animated:animated]; 130 | } 131 | 132 | - (nullable NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated completionBlock:(void (^ __nullable)(void))completion 133 | { 134 | [self setCompletionBlock:completion]; 135 | return [self popToRootViewControllerAnimated:animated]; 136 | } 137 | 138 | 139 | 140 | 141 | 142 | @end 143 | 144 | const char naviagionItemStackKey = 'a'; 145 | 146 | @implementation UINavigationItem (StatusStack) 147 | 148 | -(NSMutableArray *)statusStack 149 | { 150 | NSMutableArray *stack = objc_getAssociatedObject(self, &naviagionItemStackKey); 151 | if(!stack) 152 | { 153 | stack = [NSMutableArray arrayWithCapacity:3]; 154 | objc_setAssociatedObject(self, &naviagionItemStackKey, stack, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 155 | } 156 | 157 | return stack; 158 | } 159 | 160 | -(void)popStatus 161 | { 162 | NSMutableDictionary *mdic = [[self statusStack] lastObject]; 163 | if(mdic) 164 | { 165 | self.rightBarButtonItems = [mdic objectForKey:@"rightBarButtonItems"]; 166 | self.leftBarButtonItems = [mdic objectForKey:@"leftBarButtonItems"]; 167 | self.backBarButtonItem = [mdic objectForKey:@"backBarButtonItem"]; 168 | self.titleView = [mdic objectForKey:@"titleView"]; 169 | self.title = [mdic objectForKey:@"title"]; 170 | 171 | [[self statusStack] removeObject:mdic]; 172 | } 173 | } 174 | 175 | -(void)pushStatus 176 | { 177 | NSMutableDictionary *mdic = [NSMutableDictionary dictionaryWithCapacity:5]; 178 | 179 | if(self.rightBarButtonItems) 180 | [mdic setObject:self.rightBarButtonItems forKey:@"rightBarButtonItems"]; 181 | if(self.leftBarButtonItems) 182 | [mdic setObject:self.leftBarButtonItems forKey:@"leftBarButtonItems"]; 183 | if(self.backBarButtonItem) 184 | [mdic setObject:self.backBarButtonItem forKey:@"backBarButtonItem"]; 185 | if(self.titleView) 186 | [mdic setObject:self.titleView forKey:@"titleView"]; 187 | if(self.title) 188 | [mdic setObject:self.title forKey:@"title"]; 189 | 190 | [[self statusStack] addObject:mdic]; 191 | } 192 | 193 | @end 194 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo.xcodeproj/RRUIViewControllerExtension/UIViewController+GlobalConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+GlobalConfig.h 3 | // created by Roen Ro(罗亮富) On 2015.07 (zxllf23@163.com) 4 | 5 | #import 6 | #import "UINavigationController+RRSet.h" 7 | 8 | 9 | typedef enum { 10 | 11 | RRMethodInsertTimingBefore = 0, 12 | RRMethodInsertTimingAfter 13 | 14 | }RRMethodInsertTiming; 15 | 16 | 17 | typedef enum { 18 | 19 | RRViewControllerLifeCycleLoadView = 0, 20 | RRViewControllerLifeCycleViewDidLoad, 21 | RRViewControllerLifeCycleViewWillAppear, 22 | RRViewControllerLifeCycleViewDidAppear, 23 | RRViewControllerLifeCycleViewWillDisappear, 24 | RRViewControllerLifeCycleViewDidDisappear, 25 | 26 | }RRViewControllerLifeCycleMethod; 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | typedef void (^UIViewControllerLifecycleHookBlock) (UIViewController *viewController, BOOL animated); 31 | 32 | /* 33 | -shouldLogPageViewEvent removed 34 | -hideStatusbarOnViewAppear removed, use system default method prefersStatusBarHidden 35 | -showDefaultNavigationBackItem removed 36 | -hideNavigationBarOnViewAppear -> prefersNavigationBarHidden 37 | -transparentNavigationBarOnViewAppear -> prefersNavigationBarTransparent 38 | navigationBarTintColorOnViewAppear -> preferredNavatationBarTintColor; 39 | navigationBarItemTintColorOnViewAppear -> preferredNavigationItemTintColor 40 | navigationBarBackgroundImageOnViewAppear -> preferredNavigationBarBackgroundImage; 41 | navigationBarTitleAttributedOnViewAppear -> preferredNavigationTitleTextAttributes 42 | navigationControllerShouldPopBack -> viewControllerShouldDismiss 43 | navigationContollerSideSlipShouldPopBack -> navigationControllerAllowSidePanPopBack 44 | shouldShowPopButton -> prefersNavigationBackItemHidden 45 | isViewAppeared -> isViewAppearing 46 | */ 47 | 48 | @interface UIViewController (GlobalConfig) 49 | 50 | @property (nonatomic,readonly) BOOL isViewAppearing; 51 | 52 | 53 | /*----------------methods below are for sublclass to override ------------*/ 54 | 55 | //return YES to hide navigationBar and NO ro display navigationBar 56 | -(BOOL)prefersNavigationBarHidden; 57 | 58 | //default returns NO, you can override this method to return different values according to the current status of the viewcontroller. 59 | -(BOOL)prefersNavigationBarTransparent; 60 | 61 | -(nullable UIColor *)preferredNavatationBarTintColor; 62 | -(nullable UIColor *)preferredNavigationItemTintColor; 63 | -(nullable UIImage *)preferredNavigationBarBackgroundImage; 64 | -(nullable NSDictionary *)preferredNavigationTitleTextAttributes; 65 | -(BOOL)prefersNavigationBackItemHidden; //defaults YES for navigation root view controller and NO for others 66 | 67 | //this method is invoked each time user tapped on navigation back item to pop back or the "close" bottonItem on navigation bar to dismiss a modal view controller, return YES to continue the dismiss process. return NO to block the dismission.\ 68 | the typicall use of this method is for an editable view controller, when user taaped on the back bottonItem, you can show alert to double check if the user really want to leave the page. if the user make selected the "YES" option for the alert, you should directly call any of those method -dismissView,-dismissViewWithCompletionBlock:,-dismissViewAnimated:completionBlock:. 69 | -(BOOL)viewControllerShouldDismiss; 70 | 71 | //defaults NO for navigation root view controller and YES for others 72 | -(BOOL)navigationControllerAllowSidePanPopBack; 73 | 74 | /*----------------sublclass to override methods end------------*/ 75 | 76 | 77 | // Should be called whenever the return values for the view controller's navigation appearance have changed. 78 | -(void)setNeedsNavigationAppearanceUpdate; 79 | 80 | 81 | #pragma mark- dismiss methods 82 | - (void)dismissView; 83 | - (void)dismissViewWithCompletionBlock: (void (^ __nullable)(void))completion; 84 | - (void)dismissViewAnimated:(BOOL)animate completionBlock:(void (^ __nullable)(void))completion; 85 | 86 | #pragma mark- class methods 87 | 88 | /* 89 | Adds a block of code before/after the `lifecycleMethod` 90 | @param lifecycleMethod: the lifecycle method being hooked. 91 | @param timing: before or after the life cylce method 92 | @param block: the block code to implement the hook 93 | 94 | NOTE:the newlly set hook blocks will take place of the older ones with the same lifecycle method and same timing 95 | 96 | Optionally you can use Aspects(https://github.com/steipete/Aspects) to add hook for any objc method 97 | */ 98 | +(void)hookLifecycle:(RRViewControllerLifeCycleMethod)lifecycleMethod 99 | onTiming:(RRMethodInsertTiming)timing 100 | withBlock:(UIViewControllerLifecycleHookBlock)block; 101 | 102 | 103 | @end 104 | 105 | NS_ASSUME_NONNULL_END 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo.xcodeproj/RRUIViewControllerExtension/UIViewController+GlobalConfig.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+GlobalConfig.m 3 | // llf 2015.07 4 | 5 | #import "UIViewController+GlobalConfig.h" 6 | #import 7 | #if DEBUG 8 | #define ALERT_VIEWCONTROLLER_LEAK 1 9 | #endif 10 | 11 | 12 | static UIImage *backIndicatorImage; 13 | static NSMutableDictionary *sBeforeHookBlockMap; 14 | static NSMutableDictionary *sAfterHookBlockMap; 15 | #ifdef ALERT_VIEWCONTROLLER_LEAK 16 | static NSHashTable *sVcLeacDetectHashTable; 17 | static NSMutableArray *vcLeakWhiteSpace; 18 | #endif 19 | 20 | 21 | @implementation UIViewController (GlobalConfig) 22 | + (void)initialize 23 | { 24 | static dispatch_once_t onceToken; 25 | dispatch_once(&onceToken, ^{ 26 | 27 | Class class = [self class]; 28 | 29 | SEL originalSelector = @selector(loadView); 30 | SEL swizzledSelector = @selector(exchg_loadView); 31 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class, swizzledSelector)); 32 | 33 | originalSelector = @selector(viewDidLoad); 34 | swizzledSelector = @selector(exchg_viewDidLoad); 35 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class, swizzledSelector)); 36 | 37 | originalSelector = @selector(viewWillAppear:); 38 | swizzledSelector = @selector(exchg_viewWillAppear:); 39 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class,swizzledSelector)); 40 | 41 | originalSelector = @selector(viewDidAppear:); 42 | swizzledSelector = @selector(exchg_viewDidAppear:); 43 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class,swizzledSelector)); 44 | 45 | originalSelector = @selector(viewWillDisappear:); 46 | swizzledSelector = @selector(exchg_viewWillDisappear:); 47 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class,swizzledSelector)); 48 | 49 | originalSelector = @selector(viewDidDisappear:); 50 | swizzledSelector = @selector(exchg_viewDidDisappear:); 51 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class,swizzledSelector)); 52 | 53 | #ifdef ALERT_VIEWCONTROLLER_LEAK 54 | originalSelector = @selector(didMoveToParentViewController:); 55 | swizzledSelector = @selector(exchg_didMoveToParentViewController:); 56 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class,swizzledSelector)); 57 | sVcLeacDetectHashTable = [NSHashTable weakObjectsHashTable]; 58 | 59 | vcLeakWhiteSpace = [[NSMutableArray alloc] init]; 60 | [vcLeakWhiteSpace addObject:@"_UIRemoteInputViewController"]; 61 | #endif 62 | 63 | 64 | }); 65 | } 66 | 67 | +(void)hookLifecycle:(RRViewControllerLifeCycleMethod)lifecycleMethod 68 | onTiming:(RRMethodInsertTiming)timing 69 | withBlock:(UIViewControllerLifecycleHookBlock)block 70 | { 71 | if(block) 72 | { 73 | if(timing == RRMethodInsertTimingBefore) 74 | { 75 | if(!sBeforeHookBlockMap) 76 | sBeforeHookBlockMap = [NSMutableDictionary dictionary]; 77 | 78 | [sBeforeHookBlockMap setObject:block forKey:@(lifecycleMethod)]; 79 | } 80 | else if(timing == RRMethodInsertTimingAfter) 81 | { 82 | if(!sAfterHookBlockMap) 83 | sAfterHookBlockMap = [NSMutableDictionary dictionary]; 84 | 85 | [sAfterHookBlockMap setObject:block forKey:@(lifecycleMethod)]; 86 | } 87 | } 88 | } 89 | 90 | -(void)invokeBeforeHookForLifecycle:(RRViewControllerLifeCycleMethod)lifecycleMethod animated:(BOOL)animate 91 | { 92 | UIViewControllerLifecycleHookBlock blk = [sBeforeHookBlockMap objectForKey:@(lifecycleMethod)]; 93 | if(blk) 94 | blk(self,animate); 95 | } 96 | 97 | -(void)invokeAfterHookForLifecycle:(RRViewControllerLifeCycleMethod)lifecycleMethod animated:(BOOL)animate 98 | { 99 | UIViewControllerLifecycleHookBlock blk = [sAfterHookBlockMap objectForKey:@(lifecycleMethod)]; 100 | if(blk) 101 | blk(self,animate); 102 | } 103 | 104 | #pragma mark - exchanged life cyle methods 105 | 106 | -(void)exchg_loadView 107 | { 108 | [self invokeBeforeHookForLifecycle:RRViewControllerLifeCycleLoadView animated:NO]; 109 | 110 | [self exchg_loadView]; 111 | 112 | [self invokeAfterHookForLifecycle:RRViewControllerLifeCycleLoadView animated:NO]; 113 | } 114 | 115 | - (void)exchg_viewDidLoad 116 | { 117 | [self invokeBeforeHookForLifecycle:RRViewControllerLifeCycleViewDidLoad animated:NO]; 118 | 119 | [self exchg_viewDidLoad]; 120 | 121 | #ifdef ALERT_VIEWCONTROLLER_LEAK 122 | if([self shouldDetectMememoryLeak]) 123 | [sVcLeacDetectHashTable addObject:self]; 124 | #endif 125 | 126 | 127 | #warning removed 128 | // if ([self isKindOfClass:[UITableViewController class]]) { 129 | // UITableViewController *tableVc = (UITableViewController *)self; 130 | // tableVc.tableView.estimatedRowHeight = 0; 131 | // tableVc.tableView.estimatedSectionFooterHeight = 0; 132 | // tableVc.tableView.estimatedSectionHeaderHeight = 0; 133 | // } 134 | 135 | [self invokeAfterHookForLifecycle:RRViewControllerLifeCycleViewDidLoad animated:NO]; 136 | } 137 | 138 | - (void)exchg_viewWillAppear:(BOOL)animated 139 | { 140 | [self invokeBeforeHookForLifecycle:RRViewControllerLifeCycleViewWillAppear animated:animated]; 141 | 142 | [self exchg_viewWillAppear:animated]; 143 | 144 | #warning removed 145 | // if([self isKindOfClass:[UITabBarController class]]) 146 | // return; 147 | 148 | //在设置了leftBarButtonItem后 需要加上下面两句,navigationColtroller的手势返回才有效 149 | self.navigationController.interactivePopGestureRecognizer.delegate = self; 150 | // self.navigationController.interactivePopGestureRecognizer.enabled = YES; 151 | 152 | if(self.navigationController.applyGlobalConfig) 153 | { 154 | [self updateNavigationAppearance:animated]; 155 | } 156 | 157 | [self invokeAfterHookForLifecycle:RRViewControllerLifeCycleViewWillAppear animated:animated]; 158 | } 159 | 160 | -(void)exchg_viewDidAppear:(BOOL)animated 161 | { 162 | [self invokeBeforeHookForLifecycle:RRViewControllerLifeCycleViewDidAppear animated:animated]; 163 | 164 | [self exchg_viewDidAppear:animated]; 165 | 166 | objc_setAssociatedObject(self, @"viewAppear", @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 167 | 168 | [self setNeedsStatusBarAppearanceUpdate]; 169 | 170 | [self invokeAfterHookForLifecycle:RRViewControllerLifeCycleViewDidAppear animated:animated]; 171 | } 172 | 173 | - (void)exchg_viewWillDisappear:(BOOL)animated 174 | { 175 | [self invokeBeforeHookForLifecycle:RRViewControllerLifeCycleViewWillDisappear animated:animated]; 176 | 177 | [self exchg_viewWillDisappear:animated]; 178 | 179 | [self invokeAfterHookForLifecycle:RRViewControllerLifeCycleViewWillDisappear animated:animated]; 180 | 181 | } 182 | 183 | -(void)exchg_viewDidDisappear:(BOOL)animated 184 | { 185 | [self invokeBeforeHookForLifecycle:RRViewControllerLifeCycleViewDidDisappear animated:animated]; 186 | [self exchg_viewDidDisappear:animated]; 187 | objc_setAssociatedObject(self, @"viewAppear", @NO, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 188 | [self invokeAfterHookForLifecycle:RRViewControllerLifeCycleViewDidDisappear animated:animated]; 189 | } 190 | 191 | -(BOOL)isViewAppearing 192 | { 193 | NSNumber *v = objc_getAssociatedObject(self, @"viewAppear"); 194 | return v.boolValue; 195 | } 196 | 197 | 198 | #pragma mark- 199 | 200 | -(void)setNeedsNavigationAppearanceUpdate 201 | { 202 | if(self.isViewLoaded) 203 | [self updateNavigationAppearance:YES]; 204 | } 205 | 206 | -(void)updateNavigationAppearance:(BOOL)animated 207 | { 208 | if(!self.navigationController) 209 | return; 210 | 211 | if([self prefersNavigationBarHidden]) 212 | { 213 | [self.navigationController setNavigationBarHidden:YES animated:animated]; 214 | } 215 | else 216 | { 217 | if (!self.searchDisplayController.isActive) 218 | { 219 | [self.navigationController setNavigationBarHidden:NO animated:animated]; 220 | } 221 | } 222 | 223 | NSMutableArray *leftItems = [NSMutableArray arrayWithArray:self.navigationItem.leftBarButtonItems]; 224 | UIBarButtonItem *backItem = self.navigationBackItem; 225 | if([self prefersNavigationBackItemHidden]) 226 | { 227 | if([leftItems containsObject:backItem]) 228 | { 229 | // remove the back item 230 | [leftItems removeObject:backItem]; 231 | } 232 | } 233 | else if(![leftItems containsObject:self.navigationBackItem]) 234 | { 235 | //add back item 236 | [leftItems insertObject:backItem atIndex:0]; 237 | } 238 | self.navigationItem.leftBarButtonItems = leftItems; 239 | 240 | 241 | BOOL transparent = [self prefersNavigationBarTransparent]; 242 | [self.navigationController setNavigationBarTransparent:transparent]; 243 | if(!transparent) 244 | { 245 | #warning 这里是否欠妥,如果设置为nil的话,是不是把上次设定的默认图片也覆盖了 246 | //set navigation bar background image 247 | UIImage *bgImage = [self preferredNavigationBarBackgroundImage]; 248 | [self.navigationController.navigationBar setBackgroundImage:bgImage forBarMetrics:UIBarMetricsDefault]; 249 | [self.navigationController.navigationBar setShadowImage:nil]; 250 | } 251 | 252 | 253 | //set navigation bar tintColor 254 | [self.navigationController.navigationBar setBarTintColor:[self preferredNavatationBarTintColor]]; 255 | 256 | //set navigation bar item tintColor 257 | UIColor *barItemTintColor = [self preferredNavigationItemTintColor]; 258 | [self.navigationController.navigationBar setTintColor:barItemTintColor]; 259 | [self.navigationItem.leftBarButtonItems enumerateObjectsUsingBlock:^(UIBarButtonItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 260 | obj.tintColor = barItemTintColor; 261 | }]; 262 | [self.navigationItem.rightBarButtonItems enumerateObjectsUsingBlock:^(UIBarButtonItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 263 | obj.tintColor = barItemTintColor; 264 | }]; 265 | 266 | //set navigation bar title attributed 267 | [self.navigationController.navigationBar setTitleTextAttributes:[self preferredNavigationTitleTextAttributes]]; 268 | 269 | } 270 | 271 | 272 | -(UIBarButtonItem *)navigationBackItem 273 | { 274 | UIBarButtonItem *backItem = objc_getAssociatedObject(self, @"backItem"); 275 | if(!backItem) 276 | { 277 | if(!backIndicatorImage) 278 | backIndicatorImage = [[UIImage imageNamed:@"icon_button_return"]imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 279 | backItem = [[UIBarButtonItem alloc] initWithImage:backIndicatorImage style:UIBarButtonItemStylePlain target:self action:@selector(dismissBarButtonItemEventHandle:)]; 280 | backItem.imageInsets = UIEdgeInsetsMake(0, -2, 0, -8);// 281 | objc_setAssociatedObject(self, @"backItem", backItem, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 282 | } 283 | return backItem; 284 | } 285 | 286 | -(UIColor *)preferredNavatationBarTintColor 287 | { 288 | return [UINavigationBar appearance].barTintColor; 289 | } 290 | 291 | -(UIColor *)preferredNavigationItemTintColor 292 | { 293 | return [UINavigationBar appearance].tintColor; 294 | } 295 | 296 | -(NSDictionary *)preferredNavigationTitleTextAttributes 297 | { 298 | return [[UINavigationBar appearance] titleTextAttributes]; 299 | } 300 | 301 | -(UIImage *)preferredNavigationBarBackgroundImage 302 | { 303 | return [[UINavigationBar appearance] backgroundImageForBarMetrics:UIBarMetricsDefault];; 304 | } 305 | 306 | -(BOOL)prefersNavigationBarTransparent 307 | { 308 | return NO; 309 | } 310 | 311 | 312 | -(BOOL)prefersNavigationBarHidden 313 | { 314 | return NO; 315 | } 316 | 317 | -(BOOL)prefersNavigationBackItemHidden 318 | { 319 | BOOL hidden = NO; 320 | if(!self.navigationController.presentingViewController) 321 | { 322 | if(self.navigationController.childViewControllers.count > 0) 323 | { 324 | if(self.navigationController.viewControllers.firstObject == self) 325 | hidden = YES; 326 | } 327 | } 328 | 329 | return hidden; 330 | } 331 | 332 | 333 | -(BOOL)viewControllerShouldDismiss 334 | { 335 | return YES; 336 | } 337 | 338 | 339 | -(IBAction)dismissBarButtonItemEventHandle:(UIBarButtonItem *)backItem 340 | { 341 | if([self viewControllerShouldDismiss]) 342 | { 343 | UIViewController *popedVc = [self.navigationController popViewControllerAnimated:YES]; 344 | if(!popedVc) 345 | { 346 | [self dismissView]; 347 | } 348 | } 349 | } 350 | 351 | 352 | -(void)dismissView 353 | { 354 | [self dismissViewWithCompletionBlock:nil]; 355 | } 356 | 357 | - (void)dismissViewWithCompletionBlock: (void (^ __nullable)(void))completion 358 | { 359 | [self dismissViewAnimated:YES completionBlock:completion]; 360 | 361 | } 362 | 363 | - (void)dismissViewAnimated:(BOOL)animate completionBlock: (void (^ __nullable)(void))completion 364 | { 365 | __weak typeof(self) weak_self = self; 366 | dispatch_async(dispatch_get_main_queue(), ^{ 367 | UIViewController *popBackVc = nil; 368 | if(weak_self.navigationController) 369 | { 370 | NSArray *viewControllers = weak_self.navigationController.viewControllers; 371 | NSUInteger selfIndx = [viewControllers indexOfObject:weak_self]; 372 | if(selfIndx > 0 && selfIndx != NSNotFound) 373 | popBackVc = [viewControllers objectAtIndex:selfIndx-1]; 374 | } 375 | 376 | if(popBackVc) 377 | { 378 | [weak_self.navigationController popToViewController:popBackVc animated:animate completionBlock:completion]; 379 | } 380 | else if(weak_self.presentingViewController || weak_self.navigationController.presentingViewController) 381 | { 382 | [weak_self dismissViewControllerAnimated:animate completion:completion]; 383 | } 384 | }); 385 | } 386 | 387 | #pragma mark- 388 | //是否允许侧滑返回上一级navigationController界面 389 | -(BOOL)navigationControllerAllowSidePanPopBack 390 | { 391 | if(self.navigationController.childViewControllers.count == 1)//必须增加这个判断条件 否则会阻断用户触摸事件 392 | return NO; 393 | else 394 | { 395 | return [self viewControllerShouldDismiss]; 396 | } 397 | 398 | } 399 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 400 | { 401 | if(gestureRecognizer == self.navigationController.interactivePopGestureRecognizer) 402 | { 403 | return [self navigationControllerAllowSidePanPopBack]; 404 | } 405 | else 406 | return YES; 407 | } 408 | 409 | 410 | #pragma mark- memory leak detection 411 | 412 | #ifdef ALERT_VIEWCONTROLLER_LEAK 413 | - (void)exchg_didMoveToParentViewController:(nullable UIViewController *)parent 414 | { 415 | [self exchg_didMoveToParentViewController:parent]; 416 | 417 | if([self shouldDetectMememoryLeak]) 418 | { 419 | if(!parent) 420 | { 421 | NSString *selfAddress = [NSString stringWithFormat:@"%p",self]; 422 | [[self class] performSelector:@selector(detectLeak:) withObject:selfAddress afterDelay:1.0]; 423 | } 424 | } 425 | } 426 | 427 | +(void)detectLeak:(NSString *)vcAddress 428 | { 429 | NSMutableString *mString = [NSMutableString string]; 430 | for(UIViewController *vc in sVcLeacDetectHashTable) 431 | { 432 | NSString *curVcAddress = [NSString stringWithFormat:@"%p",vc]; 433 | if([curVcAddress isEqualToString:vcAddress] && !vc.parentViewController) 434 | { 435 | [mString appendFormat:@"%@:%@\n",NSStringFromClass([vc class]),vc.title]; 436 | } 437 | } 438 | 439 | if(mString.length) 440 | { 441 | NSString *msg = [NSString stringWithFormat:@"potential memory leak for %@",mString]; 442 | UIAlertView *alv = [[UIAlertView alloc] initWithTitle:@"Warning" message:msg delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil]; 443 | [alv show]; 444 | } 445 | } 446 | 447 | -(BOOL)shouldDetectMememoryLeak 448 | { 449 | //有一些系统内部的类无需监控,所以加一个白名单过滤 450 | return ![vcLeakWhiteSpace containsObject:NSStringFromClass([self class])]; 451 | } 452 | 453 | #endif 454 | 455 | 456 | @end 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo.xcodeproj/RRUIViewControllerExtension/resources/icon_button_return@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roen-Ro/RRViewControllerExtension/46914f604e15e332a13a3b8ed7c00af78dc1ac7f/Example/RRUIViewControllerDemo.xcodeproj/RRUIViewControllerExtension/resources/icon_button_return@2x.png -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 86AFBA26984FF871E02DF6F2 /* Pods_RRUIViewControllerDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1993F6F5B4C487C44BD6CA56 /* Pods_RRUIViewControllerDemo.framework */; }; 11 | 9F73EAF62150ECCE0046B7D2 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F73EAF52150ECCE0046B7D2 /* AppDelegate.m */; }; 12 | 9F73EAFE2150ECCE0046B7D2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9F73EAFD2150ECCE0046B7D2 /* Assets.xcassets */; }; 13 | 9F73EB012150ECCE0046B7D2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9F73EAFF2150ECCE0046B7D2 /* LaunchScreen.storyboard */; }; 14 | 9F73EB042150ECCE0046B7D2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F73EB032150ECCE0046B7D2 /* main.m */; }; 15 | 9F73EB2B2150FFA50046B7D2 /* DEMO_HiddenNaviBarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F73EB2A2150FFA50046B7D2 /* DEMO_HiddenNaviBarViewController.m */; }; 16 | 9F73EB34215100930046B7D2 /* DEMO_ImageNaviBarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F73EB33215100930046B7D2 /* DEMO_ImageNaviBarViewController.m */; }; 17 | 9F73EB362151018C0046B7D2 /* cusNavigationBar@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9F73EB352151018C0046B7D2 /* cusNavigationBar@2x.png */; }; 18 | 9F73EB412152242E0046B7D2 /* icon_apple@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9F73EB402152242E0046B7D2 /* icon_apple@2x.png */; }; 19 | 9FCFF6C521C0B03500F92633 /* DEMO_TransparentNaviBarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FCFF6C421C0B03500F92633 /* DEMO_TransparentNaviBarViewController.m */; }; 20 | 9FEF4CBF2150EEC6009292EC /* DEMO_normalMemoryLeakViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FEF4CBE2150EEC6009292EC /* DEMO_normalMemoryLeakViewController.m */; }; 21 | 9FEF4CC32150F135009292EC /* CommView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FEF4CC22150F135009292EC /* CommView.m */; }; 22 | 9FEF4CC52150F164009292EC /* CommView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9FEF4CC42150F164009292EC /* CommView.xib */; }; 23 | 9FEF4CC92150F4B8009292EC /* DynamicConfigViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FEF4CC72150F4B8009292EC /* DynamicConfigViewController.m */; }; 24 | 9FEF4CCA2150F4B8009292EC /* DynamicConfigViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9FEF4CC82150F4B8009292EC /* DynamicConfigViewController.xib */; }; 25 | 9FF94A2421536CFA00FC8F22 /* DEMO_normalMemoryLeakViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9FF94A2321536CFA00FC8F22 /* DEMO_normalMemoryLeakViewController.xib */; }; 26 | 9FF94A562154FCA000FC8F22 /* SwitchableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FF94A552154FC9F00FC8F22 /* SwitchableViewController.m */; }; 27 | B15E7EF9277995800065BF50 /* StatisticsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B15E7EF8277995800065BF50 /* StatisticsViewController.m */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 0F6EE04C246680CEDFC15A5F /* Pods-RRUIViewControllerDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RRUIViewControllerDemo.debug.xcconfig"; path = "Target Support Files/Pods-RRUIViewControllerDemo/Pods-RRUIViewControllerDemo.debug.xcconfig"; sourceTree = ""; }; 32 | 1993F6F5B4C487C44BD6CA56 /* Pods_RRUIViewControllerDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RRUIViewControllerDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 2024C47B10E3248B9B016B4E /* Pods-RRUIViewControllerDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RRUIViewControllerDemo.release.xcconfig"; path = "Target Support Files/Pods-RRUIViewControllerDemo/Pods-RRUIViewControllerDemo.release.xcconfig"; sourceTree = ""; }; 34 | 9F73EAF12150ECCE0046B7D2 /* RRUIViewControllerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RRUIViewControllerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 9F73EAF42150ECCE0046B7D2 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 36 | 9F73EAF52150ECCE0046B7D2 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 37 | 9F73EAFD2150ECCE0046B7D2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 38 | 9F73EB002150ECCE0046B7D2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 39 | 9F73EB022150ECCE0046B7D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 9F73EB032150ECCE0046B7D2 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 41 | 9F73EB292150FFA50046B7D2 /* DEMO_HiddenNaviBarViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DEMO_HiddenNaviBarViewController.h; sourceTree = ""; }; 42 | 9F73EB2A2150FFA50046B7D2 /* DEMO_HiddenNaviBarViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DEMO_HiddenNaviBarViewController.m; sourceTree = ""; }; 43 | 9F73EB32215100930046B7D2 /* DEMO_ImageNaviBarViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DEMO_ImageNaviBarViewController.h; sourceTree = ""; }; 44 | 9F73EB33215100930046B7D2 /* DEMO_ImageNaviBarViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DEMO_ImageNaviBarViewController.m; sourceTree = ""; }; 45 | 9F73EB352151018C0046B7D2 /* cusNavigationBar@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "cusNavigationBar@2x.png"; sourceTree = ""; }; 46 | 9F73EB402152242E0046B7D2 /* icon_apple@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_apple@2x.png"; sourceTree = ""; }; 47 | 9FC3AEBD25C9451100BE6C26 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/LaunchScreen.strings; sourceTree = ""; }; 48 | 9FCFF6C321C0B03500F92633 /* DEMO_TransparentNaviBarViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DEMO_TransparentNaviBarViewController.h; sourceTree = ""; }; 49 | 9FCFF6C421C0B03500F92633 /* DEMO_TransparentNaviBarViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DEMO_TransparentNaviBarViewController.m; sourceTree = ""; }; 50 | 9FEF4CBD2150EEC6009292EC /* DEMO_normalMemoryLeakViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DEMO_normalMemoryLeakViewController.h; sourceTree = ""; }; 51 | 9FEF4CBE2150EEC6009292EC /* DEMO_normalMemoryLeakViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DEMO_normalMemoryLeakViewController.m; sourceTree = ""; }; 52 | 9FEF4CC12150F135009292EC /* CommView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CommView.h; sourceTree = ""; }; 53 | 9FEF4CC22150F135009292EC /* CommView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CommView.m; sourceTree = ""; }; 54 | 9FEF4CC42150F164009292EC /* CommView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CommView.xib; sourceTree = ""; }; 55 | 9FEF4CC62150F4B8009292EC /* DynamicConfigViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DynamicConfigViewController.h; sourceTree = ""; }; 56 | 9FEF4CC72150F4B8009292EC /* DynamicConfigViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DynamicConfigViewController.m; sourceTree = ""; }; 57 | 9FEF4CC82150F4B8009292EC /* DynamicConfigViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DynamicConfigViewController.xib; sourceTree = ""; }; 58 | 9FF94A2321536CFA00FC8F22 /* DEMO_normalMemoryLeakViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DEMO_normalMemoryLeakViewController.xib; sourceTree = ""; }; 59 | 9FF94A542154FC9F00FC8F22 /* SwitchableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwitchableViewController.h; sourceTree = ""; }; 60 | 9FF94A552154FC9F00FC8F22 /* SwitchableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SwitchableViewController.m; sourceTree = ""; }; 61 | B15E7EF7277995800065BF50 /* StatisticsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StatisticsViewController.h; sourceTree = ""; }; 62 | B15E7EF8277995800065BF50 /* StatisticsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StatisticsViewController.m; sourceTree = ""; }; 63 | /* End PBXFileReference section */ 64 | 65 | /* Begin PBXFrameworksBuildPhase section */ 66 | 9F73EAEE2150ECCE0046B7D2 /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | 86AFBA26984FF871E02DF6F2 /* Pods_RRUIViewControllerDemo.framework in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | 3656CAD71D3AA9F7BED9306F /* Pods */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 0F6EE04C246680CEDFC15A5F /* Pods-RRUIViewControllerDemo.debug.xcconfig */, 81 | 2024C47B10E3248B9B016B4E /* Pods-RRUIViewControllerDemo.release.xcconfig */, 82 | ); 83 | name = Pods; 84 | path = Pods; 85 | sourceTree = ""; 86 | }; 87 | 9F73EAE82150ECCE0046B7D2 = { 88 | isa = PBXGroup; 89 | children = ( 90 | 9F73EAF32150ECCE0046B7D2 /* RRUIViewControllerDemo */, 91 | 9F73EAF22150ECCE0046B7D2 /* Products */, 92 | 3656CAD71D3AA9F7BED9306F /* Pods */, 93 | BB4F73D6F4BBBA4967D3CCA7 /* Frameworks */, 94 | ); 95 | sourceTree = ""; 96 | }; 97 | 9F73EAF22150ECCE0046B7D2 /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 9F73EAF12150ECCE0046B7D2 /* RRUIViewControllerDemo.app */, 101 | ); 102 | name = Products; 103 | sourceTree = ""; 104 | }; 105 | 9F73EAF32150ECCE0046B7D2 /* RRUIViewControllerDemo */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 9F73EAF42150ECCE0046B7D2 /* AppDelegate.h */, 109 | 9F73EAF52150ECCE0046B7D2 /* AppDelegate.m */, 110 | 9FEF4CBC2150EE93009292EC /* ViewControllers */, 111 | 9F73EAFD2150ECCE0046B7D2 /* Assets.xcassets */, 112 | 9F73EAFF2150ECCE0046B7D2 /* LaunchScreen.storyboard */, 113 | 9F73EB022150ECCE0046B7D2 /* Info.plist */, 114 | 9F73EB402152242E0046B7D2 /* icon_apple@2x.png */, 115 | 9F73EB352151018C0046B7D2 /* cusNavigationBar@2x.png */, 116 | 9F73EB032150ECCE0046B7D2 /* main.m */, 117 | ); 118 | path = RRUIViewControllerDemo; 119 | sourceTree = ""; 120 | }; 121 | 9F73EB3D215213C50046B7D2 /* DynamicConfig */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 9FEF4CC62150F4B8009292EC /* DynamicConfigViewController.h */, 125 | 9FEF4CC72150F4B8009292EC /* DynamicConfigViewController.m */, 126 | 9FEF4CC82150F4B8009292EC /* DynamicConfigViewController.xib */, 127 | ); 128 | path = DynamicConfig; 129 | sourceTree = ""; 130 | }; 131 | 9FEF4CBC2150EE93009292EC /* ViewControllers */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 9FEF4CC02150F0EC009292EC /* commView */, 135 | 9FEF4CBD2150EEC6009292EC /* DEMO_normalMemoryLeakViewController.h */, 136 | 9FEF4CBE2150EEC6009292EC /* DEMO_normalMemoryLeakViewController.m */, 137 | 9FF94A2321536CFA00FC8F22 /* DEMO_normalMemoryLeakViewController.xib */, 138 | 9F73EB292150FFA50046B7D2 /* DEMO_HiddenNaviBarViewController.h */, 139 | 9F73EB2A2150FFA50046B7D2 /* DEMO_HiddenNaviBarViewController.m */, 140 | 9FCFF6C321C0B03500F92633 /* DEMO_TransparentNaviBarViewController.h */, 141 | 9FCFF6C421C0B03500F92633 /* DEMO_TransparentNaviBarViewController.m */, 142 | 9F73EB32215100930046B7D2 /* DEMO_ImageNaviBarViewController.h */, 143 | 9F73EB33215100930046B7D2 /* DEMO_ImageNaviBarViewController.m */, 144 | 9FF94A542154FC9F00FC8F22 /* SwitchableViewController.h */, 145 | 9FF94A552154FC9F00FC8F22 /* SwitchableViewController.m */, 146 | B15E7EF7277995800065BF50 /* StatisticsViewController.h */, 147 | B15E7EF8277995800065BF50 /* StatisticsViewController.m */, 148 | 9F73EB3D215213C50046B7D2 /* DynamicConfig */, 149 | ); 150 | path = ViewControllers; 151 | sourceTree = ""; 152 | }; 153 | 9FEF4CC02150F0EC009292EC /* commView */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 9FEF4CC12150F135009292EC /* CommView.h */, 157 | 9FEF4CC22150F135009292EC /* CommView.m */, 158 | 9FEF4CC42150F164009292EC /* CommView.xib */, 159 | ); 160 | path = commView; 161 | sourceTree = ""; 162 | }; 163 | BB4F73D6F4BBBA4967D3CCA7 /* Frameworks */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | 1993F6F5B4C487C44BD6CA56 /* Pods_RRUIViewControllerDemo.framework */, 167 | ); 168 | name = Frameworks; 169 | sourceTree = ""; 170 | }; 171 | /* End PBXGroup section */ 172 | 173 | /* Begin PBXNativeTarget section */ 174 | 9F73EAF02150ECCE0046B7D2 /* RRUIViewControllerDemo */ = { 175 | isa = PBXNativeTarget; 176 | buildConfigurationList = 9F73EB072150ECCE0046B7D2 /* Build configuration list for PBXNativeTarget "RRUIViewControllerDemo" */; 177 | buildPhases = ( 178 | B0F6DDD5C99AF36CBD517A3F /* [CP] Check Pods Manifest.lock */, 179 | 9F73EAED2150ECCE0046B7D2 /* Sources */, 180 | 9F73EAEE2150ECCE0046B7D2 /* Frameworks */, 181 | 9F73EAEF2150ECCE0046B7D2 /* Resources */, 182 | 97E28A321ACE9DA0A395BC81 /* [CP] Embed Pods Frameworks */, 183 | ); 184 | buildRules = ( 185 | ); 186 | dependencies = ( 187 | ); 188 | name = RRUIViewControllerDemo; 189 | productName = RRUIViewControllerDemo; 190 | productReference = 9F73EAF12150ECCE0046B7D2 /* RRUIViewControllerDemo.app */; 191 | productType = "com.apple.product-type.application"; 192 | }; 193 | /* End PBXNativeTarget section */ 194 | 195 | /* Begin PBXProject section */ 196 | 9F73EAE92150ECCE0046B7D2 /* Project object */ = { 197 | isa = PBXProject; 198 | attributes = { 199 | LastUpgradeCheck = 0920; 200 | ORGANIZATIONNAME = "深圳市两步路信息技术有限公司"; 201 | TargetAttributes = { 202 | 9F73EAF02150ECCE0046B7D2 = { 203 | CreatedOnToolsVersion = 9.2; 204 | ProvisioningStyle = Automatic; 205 | }; 206 | }; 207 | }; 208 | buildConfigurationList = 9F73EAEC2150ECCE0046B7D2 /* Build configuration list for PBXProject "RRUIViewControllerDemo" */; 209 | compatibilityVersion = "Xcode 8.0"; 210 | developmentRegion = en; 211 | hasScannedForEncodings = 0; 212 | knownRegions = ( 213 | en, 214 | Base, 215 | ar, 216 | ); 217 | mainGroup = 9F73EAE82150ECCE0046B7D2; 218 | productRefGroup = 9F73EAF22150ECCE0046B7D2 /* Products */; 219 | projectDirPath = ""; 220 | projectRoot = ""; 221 | targets = ( 222 | 9F73EAF02150ECCE0046B7D2 /* RRUIViewControllerDemo */, 223 | ); 224 | }; 225 | /* End PBXProject section */ 226 | 227 | /* Begin PBXResourcesBuildPhase section */ 228 | 9F73EAEF2150ECCE0046B7D2 /* Resources */ = { 229 | isa = PBXResourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 9F73EB412152242E0046B7D2 /* icon_apple@2x.png in Resources */, 233 | 9F73EB362151018C0046B7D2 /* cusNavigationBar@2x.png in Resources */, 234 | 9F73EB012150ECCE0046B7D2 /* LaunchScreen.storyboard in Resources */, 235 | 9FEF4CC52150F164009292EC /* CommView.xib in Resources */, 236 | 9FEF4CCA2150F4B8009292EC /* DynamicConfigViewController.xib in Resources */, 237 | 9FF94A2421536CFA00FC8F22 /* DEMO_normalMemoryLeakViewController.xib in Resources */, 238 | 9F73EAFE2150ECCE0046B7D2 /* Assets.xcassets in Resources */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXResourcesBuildPhase section */ 243 | 244 | /* Begin PBXShellScriptBuildPhase section */ 245 | 97E28A321ACE9DA0A395BC81 /* [CP] Embed Pods Frameworks */ = { 246 | isa = PBXShellScriptBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | ); 250 | inputPaths = ( 251 | "${PODS_ROOT}/Target Support Files/Pods-RRUIViewControllerDemo/Pods-RRUIViewControllerDemo-frameworks.sh", 252 | "${BUILT_PRODUCTS_DIR}/RRViewControllerExtension/RRViewControllerExtension.framework", 253 | ); 254 | name = "[CP] Embed Pods Frameworks"; 255 | outputPaths = ( 256 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RRViewControllerExtension.framework", 257 | ); 258 | runOnlyForDeploymentPostprocessing = 0; 259 | shellPath = /bin/sh; 260 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RRUIViewControllerDemo/Pods-RRUIViewControllerDemo-frameworks.sh\"\n"; 261 | showEnvVarsInLog = 0; 262 | }; 263 | B0F6DDD5C99AF36CBD517A3F /* [CP] Check Pods Manifest.lock */ = { 264 | isa = PBXShellScriptBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | ); 268 | inputFileListPaths = ( 269 | ); 270 | inputPaths = ( 271 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 272 | "${PODS_ROOT}/Manifest.lock", 273 | ); 274 | name = "[CP] Check Pods Manifest.lock"; 275 | outputFileListPaths = ( 276 | ); 277 | outputPaths = ( 278 | "$(DERIVED_FILE_DIR)/Pods-RRUIViewControllerDemo-checkManifestLockResult.txt", 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | shellPath = /bin/sh; 282 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 283 | showEnvVarsInLog = 0; 284 | }; 285 | /* End PBXShellScriptBuildPhase section */ 286 | 287 | /* Begin PBXSourcesBuildPhase section */ 288 | 9F73EAED2150ECCE0046B7D2 /* Sources */ = { 289 | isa = PBXSourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | 9FEF4CC32150F135009292EC /* CommView.m in Sources */, 293 | 9FF94A562154FCA000FC8F22 /* SwitchableViewController.m in Sources */, 294 | 9F73EB042150ECCE0046B7D2 /* main.m in Sources */, 295 | 9FEF4CBF2150EEC6009292EC /* DEMO_normalMemoryLeakViewController.m in Sources */, 296 | 9FCFF6C521C0B03500F92633 /* DEMO_TransparentNaviBarViewController.m in Sources */, 297 | 9F73EAF62150ECCE0046B7D2 /* AppDelegate.m in Sources */, 298 | 9F73EB34215100930046B7D2 /* DEMO_ImageNaviBarViewController.m in Sources */, 299 | B15E7EF9277995800065BF50 /* StatisticsViewController.m in Sources */, 300 | 9FEF4CC92150F4B8009292EC /* DynamicConfigViewController.m in Sources */, 301 | 9F73EB2B2150FFA50046B7D2 /* DEMO_HiddenNaviBarViewController.m in Sources */, 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | }; 305 | /* End PBXSourcesBuildPhase section */ 306 | 307 | /* Begin PBXVariantGroup section */ 308 | 9F73EAFF2150ECCE0046B7D2 /* LaunchScreen.storyboard */ = { 309 | isa = PBXVariantGroup; 310 | children = ( 311 | 9F73EB002150ECCE0046B7D2 /* Base */, 312 | 9FC3AEBD25C9451100BE6C26 /* ar */, 313 | ); 314 | name = LaunchScreen.storyboard; 315 | sourceTree = ""; 316 | }; 317 | /* End PBXVariantGroup section */ 318 | 319 | /* Begin XCBuildConfiguration section */ 320 | 9F73EB052150ECCE0046B7D2 /* Debug */ = { 321 | isa = XCBuildConfiguration; 322 | buildSettings = { 323 | ALWAYS_SEARCH_USER_PATHS = NO; 324 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 325 | CLANG_ANALYZER_NONNULL = YES; 326 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 327 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 328 | CLANG_CXX_LIBRARY = "libc++"; 329 | CLANG_ENABLE_MODULES = YES; 330 | CLANG_ENABLE_OBJC_ARC = YES; 331 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 332 | CLANG_WARN_BOOL_CONVERSION = YES; 333 | CLANG_WARN_COMMA = YES; 334 | CLANG_WARN_CONSTANT_CONVERSION = YES; 335 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 336 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 337 | CLANG_WARN_EMPTY_BODY = YES; 338 | CLANG_WARN_ENUM_CONVERSION = YES; 339 | CLANG_WARN_INFINITE_RECURSION = YES; 340 | CLANG_WARN_INT_CONVERSION = YES; 341 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 343 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 344 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 345 | CLANG_WARN_STRICT_PROTOTYPES = YES; 346 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 347 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 348 | CLANG_WARN_UNREACHABLE_CODE = YES; 349 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 350 | CODE_SIGN_IDENTITY = "iPhone Developer"; 351 | COPY_PHASE_STRIP = NO; 352 | DEBUG_INFORMATION_FORMAT = dwarf; 353 | ENABLE_STRICT_OBJC_MSGSEND = YES; 354 | ENABLE_TESTABILITY = YES; 355 | GCC_C_LANGUAGE_STANDARD = gnu11; 356 | GCC_DYNAMIC_NO_PIC = NO; 357 | GCC_NO_COMMON_BLOCKS = YES; 358 | GCC_OPTIMIZATION_LEVEL = 0; 359 | GCC_PREPROCESSOR_DEFINITIONS = ( 360 | "DEBUG=1", 361 | "$(inherited)", 362 | ); 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 370 | MTL_ENABLE_DEBUG_INFO = YES; 371 | ONLY_ACTIVE_ARCH = YES; 372 | SDKROOT = iphoneos; 373 | }; 374 | name = Debug; 375 | }; 376 | 9F73EB062150ECCE0046B7D2 /* Release */ = { 377 | isa = XCBuildConfiguration; 378 | buildSettings = { 379 | ALWAYS_SEARCH_USER_PATHS = NO; 380 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 381 | CLANG_ANALYZER_NONNULL = YES; 382 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 383 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 384 | CLANG_CXX_LIBRARY = "libc++"; 385 | CLANG_ENABLE_MODULES = YES; 386 | CLANG_ENABLE_OBJC_ARC = YES; 387 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 388 | CLANG_WARN_BOOL_CONVERSION = YES; 389 | CLANG_WARN_COMMA = YES; 390 | CLANG_WARN_CONSTANT_CONVERSION = YES; 391 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 392 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 393 | CLANG_WARN_EMPTY_BODY = YES; 394 | CLANG_WARN_ENUM_CONVERSION = YES; 395 | CLANG_WARN_INFINITE_RECURSION = YES; 396 | CLANG_WARN_INT_CONVERSION = YES; 397 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 398 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 399 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 400 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 401 | CLANG_WARN_STRICT_PROTOTYPES = YES; 402 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 403 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 404 | CLANG_WARN_UNREACHABLE_CODE = YES; 405 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 406 | CODE_SIGN_IDENTITY = "iPhone Developer"; 407 | COPY_PHASE_STRIP = NO; 408 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 409 | ENABLE_NS_ASSERTIONS = NO; 410 | ENABLE_STRICT_OBJC_MSGSEND = YES; 411 | GCC_C_LANGUAGE_STANDARD = gnu11; 412 | GCC_NO_COMMON_BLOCKS = YES; 413 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 414 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 415 | GCC_WARN_UNDECLARED_SELECTOR = YES; 416 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 417 | GCC_WARN_UNUSED_FUNCTION = YES; 418 | GCC_WARN_UNUSED_VARIABLE = YES; 419 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 420 | MTL_ENABLE_DEBUG_INFO = NO; 421 | SDKROOT = iphoneos; 422 | VALIDATE_PRODUCT = YES; 423 | }; 424 | name = Release; 425 | }; 426 | 9F73EB082150ECCE0046B7D2 /* Debug */ = { 427 | isa = XCBuildConfiguration; 428 | baseConfigurationReference = 0F6EE04C246680CEDFC15A5F /* Pods-RRUIViewControllerDemo.debug.xcconfig */; 429 | buildSettings = { 430 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 431 | CODE_SIGN_STYLE = Automatic; 432 | DEVELOPMENT_TEAM = ""; 433 | INFOPLIST_FILE = RRUIViewControllerDemo/Info.plist; 434 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 435 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 436 | PRODUCT_BUNDLE_IDENTIFIER = com.2blulu.rrvc.demo.RRUIViewControllerDemo; 437 | PRODUCT_NAME = "$(TARGET_NAME)"; 438 | TARGETED_DEVICE_FAMILY = "1,2"; 439 | }; 440 | name = Debug; 441 | }; 442 | 9F73EB092150ECCE0046B7D2 /* Release */ = { 443 | isa = XCBuildConfiguration; 444 | baseConfigurationReference = 2024C47B10E3248B9B016B4E /* Pods-RRUIViewControllerDemo.release.xcconfig */; 445 | buildSettings = { 446 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 447 | CODE_SIGN_STYLE = Automatic; 448 | DEVELOPMENT_TEAM = ""; 449 | INFOPLIST_FILE = RRUIViewControllerDemo/Info.plist; 450 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 451 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 452 | PRODUCT_BUNDLE_IDENTIFIER = com.2blulu.rrvc.demo.RRUIViewControllerDemo; 453 | PRODUCT_NAME = "$(TARGET_NAME)"; 454 | TARGETED_DEVICE_FAMILY = "1,2"; 455 | }; 456 | name = Release; 457 | }; 458 | /* End XCBuildConfiguration section */ 459 | 460 | /* Begin XCConfigurationList section */ 461 | 9F73EAEC2150ECCE0046B7D2 /* Build configuration list for PBXProject "RRUIViewControllerDemo" */ = { 462 | isa = XCConfigurationList; 463 | buildConfigurations = ( 464 | 9F73EB052150ECCE0046B7D2 /* Debug */, 465 | 9F73EB062150ECCE0046B7D2 /* Release */, 466 | ); 467 | defaultConfigurationIsVisible = 0; 468 | defaultConfigurationName = Release; 469 | }; 470 | 9F73EB072150ECCE0046B7D2 /* Build configuration list for PBXNativeTarget "RRUIViewControllerDemo" */ = { 471 | isa = XCConfigurationList; 472 | buildConfigurations = ( 473 | 9F73EB082150ECCE0046B7D2 /* Debug */, 474 | 9F73EB092150ECCE0046B7D2 /* Release */, 475 | ); 476 | defaultConfigurationIsVisible = 0; 477 | defaultConfigurationName = Release; 478 | }; 479 | /* End XCConfigurationList section */ 480 | }; 481 | rootObject = 9F73EAE92150ECCE0046B7D2 /* Project object */; 482 | } 483 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. 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 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "RRViewControllerExtension.h" 11 | #import "DEMO_normalMemoryLeakViewController.h" 12 | #import "CommView.h" 13 | @interface AppDelegate () 14 | 15 | @end 16 | 17 | @implementation AppDelegate 18 | 19 | 20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 21 | { 22 | //setup app appearance 23 | [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:0 green:0.4 blue:0.7 alpha:1.0]]; 24 | [[UINavigationBar appearance] setTintColor:[UIColor whiteColor]]; 25 | NSDictionary * dict = [NSDictionary dictionaryWithObject:[UIColor yellowColor] forKey:NSForegroundColorAttributeName]; 26 | [[UINavigationBar appearance] setTitleTextAttributes:dict]; 27 | // [[UIView appearance] setBackgroundColor:[UIColor whiteColor]]; 28 | 29 | DEMO_normalMemoryLeakViewController *rtVc = [[DEMO_normalMemoryLeakViewController alloc] init]; 30 | UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:rtVc]; 31 | 32 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 33 | self.window.backgroundColor = [UIColor whiteColor]; 34 | [self.window makeKeyAndVisible]; 35 | self.window.rootViewController = navi; 36 | 37 | 38 | [UIViewController hookLifecycle:RRViewControllerLifeCycleViewDidLoad onTiming:RRMethodInsertTimingAfter withBlock:^(UIViewController * _Nonnull viewController, BOOL animated) { 39 | 40 | NSLog(@"%@ viewDidLoad",NSStringFromClass([viewController class])); 41 | 42 | if([NSStringFromClass([viewController class]) hasPrefix:@"DEMO_"]) 43 | { 44 | // viewController.view.backgroundColor = [UIColor whiteColor]; 45 | // if(!viewController.title) 46 | // viewController.title = NSStringFromClass([viewController class]); 47 | // 48 | // UIBarButtonItem *ritm = [[UIBarButtonItem alloc] initWithTitle:[NSString stringWithFormat:@"R%d",rand()%10] style:UIBarButtonItemStylePlain target:nil action:nil]; 49 | // 50 | // viewController.navigationItem.rightBarButtonItem = ritm; 51 | 52 | CommView *v = [[NSBundle mainBundle] loadNibNamed:@"CommView" owner:nil options:nil].firstObject; 53 | v.viewController = viewController; 54 | v.frame = CGRectMake(0, 96, viewController.view.frame.size.width, v.frame.size.height); 55 | [viewController.view addSubview:v]; 56 | 57 | } 58 | }]; 59 | 60 | 61 | 62 | [UIViewController hookLifecycle:RRViewControllerLifeCycleViewWillAppear 63 | onTiming:RRMethodInsertTimingBefore 64 | withBlock:^(UIViewController * _Nonnull viewController, BOOL animated) { 65 | 66 | NSLog(@"%@ ViewWillAppear animated:%d",NSStringFromClass([viewController class]),animated); 67 | // [MyLog logEnterPage:NSStringFromClass([viewController class])]; 68 | }]; 69 | 70 | [UIViewController hookLifecycle:RRViewControllerLifeCycleViewDidDisappear 71 | onTiming:RRMethodInsertTimingAfter 72 | withBlock:^(UIViewController * _Nonnull viewController, BOOL animated) { 73 | 74 | // [MyLog logLeavePage:NSStringFromClass([viewController class])]; 75 | NSLog(@"%@ viewWillDisappear animated:%d",NSStringFromClass([viewController class]),animated); 76 | }]; 77 | // 78 | 79 | return YES; 80 | } 81 | 82 | 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/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 | } -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/Assets.xcassets/TintBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "display-p3", 11 | "components" : { 12 | "red" : "0.000", 13 | "alpha" : "1.000", 14 | "blue" : "1.000", 15 | "green" : "0.500" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/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 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/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 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/DEMO_HiddenNaviBarViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HiddenNaviBarViewController.h 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //navigaion bar hidden 12 | @interface DEMO_HiddenNaviBarViewController : UIViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/DEMO_HiddenNaviBarViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HiddenNaviBarViewController.m 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import "DEMO_HiddenNaviBarViewController.h" 10 | 11 | @interface DEMO_HiddenNaviBarViewController () 12 | 13 | @end 14 | 15 | @implementation DEMO_HiddenNaviBarViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view. 20 | self.view.backgroundColor = [UIColor lightGrayColor]; 21 | } 22 | 23 | -(BOOL)prefersNavigationBarHidden 24 | { 25 | return YES; 26 | } 27 | 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/DEMO_ImageNaviBarViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImageNaviBarViewController.h 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //navigation bar with image 12 | @interface DEMO_ImageNaviBarViewController : UIViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/DEMO_ImageNaviBarViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ImageNaviBarViewController.m 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import "DEMO_ImageNaviBarViewController.h" 10 | 11 | 12 | 13 | @implementation DEMO_ImageNaviBarViewController 14 | 15 | - (void)viewDidLoad 16 | { 17 | [super viewDidLoad]; 18 | self.view.backgroundColor = [UIColor colorWithWhite:.88 alpha:1]; 19 | UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonItemStylePlain target:nil action:nil]; 20 | self.navigationItem.rightBarButtonItem = rightItem; 21 | self.title = @"IMAGE"; 22 | self.view.backgroundColor = [UIColor yellowColor]; 23 | } 24 | 25 | -(nullable UIImage *)preferredNavigationBarBackgroundImage 26 | { 27 | return [UIImage imageNamed:@"cusNavigationBar"]; 28 | } 29 | 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/DEMO_TransparentNaviBarViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DEMO_TransparentNaviBarViewController.h 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富 on 2018/12/12. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | 13 | @interface DEMO_TransparentNaviBarViewController : UIViewController 14 | 15 | @end 16 | 17 | 18 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/DEMO_TransparentNaviBarViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DEMO_TransparentNaviBarViewController.m 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富 on 2018/12/12. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import "DEMO_TransparentNaviBarViewController.h" 10 | 11 | 12 | @implementation DEMO_TransparentNaviBarViewController 13 | 14 | -(void)viewDidLoad { 15 | [super viewDidLoad]; 16 | self.view.backgroundColor = [UIColor whiteColor]; 17 | } 18 | 19 | -(BOOL)prefersNavigationBarTransparent 20 | { 21 | return YES; 22 | } 23 | 24 | -(nullable UIColor *)preferredNavigationItemColor { 25 | return UIColor.cyanColor; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/DEMO_normalMemoryLeakViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppRootViewController.h 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //normal navigation bar appearance, memory leak 12 | @interface DEMO_normalMemoryLeakViewController : UIViewController 13 | 14 | @end 15 | 16 | 17 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/DEMO_normalMemoryLeakViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppRootViewController.m 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import "DEMO_normalMemoryLeakViewController.h" 10 | #import "RRViewControllerExtension.h" 11 | #import "SwitchableViewController.h" 12 | #import "DynamicConfigViewController.h" 13 | #import "DEMO_ImageNaviBarViewController.h" 14 | #import "DEMO_normalMemoryLeakViewController.h" 15 | #import 16 | 17 | //only for memmory leak debug 仅用于vc内存泄露调试 18 | NSMutableArray *sVcMemLeakDebugArray; 19 | static int sCreatedCount; 20 | 21 | @implementation DEMO_normalMemoryLeakViewController 22 | 23 | +(void)load 24 | { 25 | if(!sVcMemLeakDebugArray) 26 | sVcMemLeakDebugArray = [NSMutableArray array]; 27 | } 28 | 29 | -(instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 30 | { 31 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 32 | // Do any additional setup after loading the view. 33 | sCreatedCount++; 34 | self.title = [NSString stringWithFormat:@"Normal %d",sCreatedCount]; 35 | 36 | return self; 37 | } 38 | 39 | - (void)viewDidLoad 40 | { 41 | [super viewDidLoad]; 42 | 43 | [sVcMemLeakDebugArray addObject:self]; 44 | } 45 | 46 | 47 | -(BOOL)viewControllerShouldDismiss 48 | { 49 | UIAlertController *alvCtrl = [UIAlertController alertControllerWithTitle:@"Tips" message:@"Override -[UIViewController viewControllerShouldDismiss] method and return NO will block the view dismiss, you can show an alert in this method" preferredStyle:UIAlertControllerStyleAlert]; 50 | 51 | [alvCtrl addAction:[UIAlertAction actionWithTitle:@"Stay" style:UIAlertActionStyleCancel handler:nil]]; 52 | [alvCtrl addAction:[UIAlertAction actionWithTitle:@"Leave" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 53 | 54 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 55 | [self dismissView]; 56 | }); 57 | 58 | 59 | }]]; 60 | 61 | [self presentViewController:alvCtrl animated:YES completion:nil]; 62 | 63 | return NO; 64 | } 65 | 66 | 67 | 68 | -(IBAction)presentNavigationWrappedVc:(id)sender 69 | { 70 | DEMO_normalMemoryLeakViewController *vc = [[DEMO_normalMemoryLeakViewController alloc] init]; 71 | UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:vc]; 72 | navi.defaultNavatationBarColor = [UIColor greenColor]; 73 | navi.defaultNavigationTitleTextAttributes = [NSDictionary dictionaryWithObject:[UIColor blueColor] forKey:NSForegroundColorAttributeName]; 74 | [self presentViewController:navi animated:YES completion:nil]; 75 | } 76 | 77 | 78 | 79 | 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/DEMO_normalMemoryLeakViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/DynamicConfig/DynamicConfigViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicConfigViewController.h 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DynamicConfigViewController : UIViewController 14 | 15 | @property (nonatomic) BOOL navigationBarHidden; 16 | @property (nonatomic) BOOL navigationBarTransparent; 17 | @property (nonatomic, copy) UIColor *navigationBarColor; 18 | @property (nonatomic, copy) UIColor *navigationItemColor; 19 | @property (nonatomic, copy) NSDictionary *navigationTitleAttribute; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/DynamicConfig/DynamicConfigViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicConfigViewController.m 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | @import RRViewControllerExtension; 10 | #import "DynamicConfigViewController.h" 11 | #import "DEMO_ImageNaviBarViewController.h" 12 | #import "DEMO_HiddenNaviBarViewController.h" 13 | #import "DEMO_TransparentNaviBarViewController.h" 14 | 15 | // do not confused with code in this file, most of it is for the 16 | @implementation DynamicConfigViewController 17 | { 18 | 19 | __weak IBOutlet UIButton *_hiddenBtn; 20 | __weak IBOutlet UIButton *_transparentBtn; 21 | __weak IBOutlet UIButton *_barColorBtn; 22 | __weak IBOutlet UIButton *_itemColorBtn; 23 | __weak IBOutlet UIButton *_titleAttributeBtn; 24 | 25 | } 26 | 27 | - (void)viewDidLoad 28 | { 29 | [super viewDidLoad]; 30 | self.title = @"dynamic appearance"; 31 | 32 | UIBarButtonItem *ritm2 = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"icon_apple"] style:UIBarButtonItemStylePlain target:nil action:nil]; 33 | self.navigationItem.rightBarButtonItem = ritm2; 34 | 35 | self.view.backgroundColor = [UIColor darkGrayColor]; 36 | 37 | [self randomChange:nil]; 38 | 39 | } 40 | 41 | 42 | #pragma mark- appearance 43 | -(BOOL)prefersNavigationBarHidden 44 | { 45 | return self.navigationBarHidden; 46 | } 47 | 48 | -(BOOL)prefersNavigationBarTransparent 49 | { 50 | return self.navigationBarTransparent; 51 | } 52 | 53 | -(nullable UIColor *)preferredNavigationItemColor 54 | { 55 | return self.navigationItemColor; 56 | } 57 | 58 | -(nullable UIColor *)preferredNavatationBarColor 59 | { 60 | return self.navigationBarColor; 61 | } 62 | 63 | -(nullable NSDictionary *)preferredNavigationTitleTextAttributes 64 | { 65 | return self.navigationTitleAttribute; 66 | } 67 | 68 | -(void)forceNavigationAppearanceUpdate 69 | { 70 | if(self.navigationBarHidden) 71 | { 72 | [_hiddenBtn setTitle:@"navigation bar is hidden" forState:UIControlStateNormal]; 73 | [_hiddenBtn setTitleColor:[UIColor grayColor] forState:UIControlStateNormal]; 74 | } 75 | else 76 | { 77 | [_hiddenBtn setTitle:@"navigation bar is shown" forState:UIControlStateNormal]; 78 | [_hiddenBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 79 | } 80 | 81 | if(self.navigationBarTransparent) 82 | { 83 | [_transparentBtn setTitle:@"navigation bar is transparent" forState:UIControlStateNormal]; 84 | [_transparentBtn setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal]; 85 | } 86 | else 87 | { 88 | [_transparentBtn setTitle:@"navigation bar is opaque" forState:UIControlStateNormal]; 89 | [_transparentBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 90 | } 91 | 92 | [_barColorBtn setTitleColor:self.navigationBarColor forState:UIControlStateNormal]; 93 | [_itemColorBtn setTitleColor:self.self.navigationItemColor forState:UIControlStateNormal]; 94 | NSAttributedString *atrStr = [[NSAttributedString alloc] initWithString:_titleAttributeBtn.titleLabel.text attributes:self.navigationTitleAttribute]; 95 | [_titleAttributeBtn setAttributedTitle:atrStr forState:UIControlStateNormal]; 96 | 97 | 98 | [self updateNavigationAppearance:YES]; 99 | } 100 | 101 | #pragma mark- button actions 102 | 103 | - (IBAction)hiddenSwitch:(UIButton *)sender 104 | { 105 | self.navigationBarHidden = !self.navigationBarHidden; 106 | [self forceNavigationAppearanceUpdate]; 107 | } 108 | 109 | - (IBAction)opaqueSwitch:(UIButton *)sender { 110 | 111 | self.navigationBarTransparent = !self.navigationBarTransparent; 112 | self.navigationBarHidden = NO; 113 | [self forceNavigationAppearanceUpdate]; 114 | } 115 | 116 | - (IBAction)barColorChange:(UIButton *)sender { 117 | 118 | self.navigationBarColor = [self randomColor]; 119 | self.navigationBarHidden = NO; 120 | self.navigationBarTransparent = NO; 121 | [self forceNavigationAppearanceUpdate]; 122 | } 123 | 124 | - (IBAction)itemColorChange:(UIButton *)sender { 125 | 126 | self.self.navigationItemColor = [self randomColor]; 127 | self.navigationBarHidden = NO; 128 | [self forceNavigationAppearanceUpdate]; 129 | } 130 | 131 | - (IBAction)titleAttributeChange:(UIButton *)sender { 132 | 133 | self.navigationTitleAttribute = @{NSForegroundColorAttributeName:[self randomColor],NSFontAttributeName:[UIFont systemFontOfSize:rand()%10+10]}; 134 | self.navigationBarHidden = NO; 135 | [self forceNavigationAppearanceUpdate]; 136 | } 137 | 138 | -(IBAction)randomChange:(id)sender 139 | { 140 | self.navigationBarHidden = NO; 141 | self.navigationBarTransparent = !rand()%3; 142 | self.navigationBarColor = [self randomColor]; 143 | self.navigationItemColor = [self randomColor]; 144 | self.navigationTitleAttribute = @{NSForegroundColorAttributeName:[self randomColor],NSFontAttributeName:[UIFont systemFontOfSize:rand()%10+10]}; 145 | 146 | [self forceNavigationAppearanceUpdate]; 147 | } 148 | 149 | -(IBAction)pushToImageNaviBar:(UIButton *)sender 150 | { 151 | DEMO_ImageNaviBarViewController *vc = [[DEMO_ImageNaviBarViewController alloc] init]; 152 | [self.navigationController pushViewController:vc animated:YES completionBlock:^{ 153 | NSLog(@"finish push to DEMO_ImageNaviBarViewController"); 154 | }]; 155 | 156 | } 157 | 158 | - (IBAction)pushToTransparent:(id)sender { 159 | 160 | DEMO_TransparentNaviBarViewController *vc = [[DEMO_TransparentNaviBarViewController alloc] init]; 161 | [self.navigationController pushViewController:vc animated:YES completionBlock:^{ 162 | NSLog(@"finish push to DEMO_TransparentNaviBarViewController"); 163 | }]; 164 | } 165 | 166 | - (IBAction)pushToHiddenBar:(id)sender { 167 | 168 | DEMO_HiddenNaviBarViewController *vc = [[DEMO_HiddenNaviBarViewController alloc] init]; 169 | [self.navigationController pushViewController:vc animated:YES completionBlock:^{ 170 | NSLog(@"finish push to DEMO_HiddenNaviBarViewController"); 171 | }]; 172 | } 173 | 174 | #pragma mark- 175 | 176 | -(UIColor *)randomColor 177 | { 178 | return [UIColor colorWithRed:rand()%20/20.0 green:rand()%20/20.0 blue:rand()%20/20.0 alpha:1]; 179 | } 180 | 181 | 182 | 183 | @end 184 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/DynamicConfig/DynamicConfigViewController.xib: -------------------------------------------------------------------------------- 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 | 40 | 48 | 58 | 68 | 78 | 88 | 98 | 110 | 122 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/StatisticsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // StatisticsViewController.h 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by luoliangfu on 2021/12/27. 6 | // Copyright © 2021 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface StatisticsViewController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/StatisticsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // StatisticsViewController.m 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by luoliangfu on 2021/12/27. 6 | // Copyright © 2021 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import "StatisticsViewController.h" 10 | #import "UIViewController+RRStatistics.h" 11 | 12 | @interface StatisticsViewController () 13 | 14 | @end 15 | 16 | @implementation StatisticsViewController { 17 | UITextView *_textView; 18 | } 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | self.title = @"Statistics"; 23 | _textView = [[UITextView alloc] init]; 24 | _textView.editable = NO; 25 | _textView.frame = self.view.bounds; 26 | [self.view addSubview:_textView]; 27 | 28 | 29 | NSString *s = [UIViewController stringifyStatisticsWithTopStayedByCount:20]; 30 | _textView.text = s; 31 | } 32 | 33 | 34 | 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/SwitchableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchableViewController.h 3 | // 2bulu-QuanZi 4 | // 5 | // Created by 罗亮富 on 14-8-14. 6 | // Copyright (c) 2014年 Lolaage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol SwitchableViewControllerDelegate; 12 | 13 | @interface SwitchableViewController : UIViewController 14 | { 15 | @protected 16 | NSArray *_viewControllers; 17 | } 18 | 19 | 20 | -(id)initWithViewControllers:(NSArray *)viewControllers; 21 | 22 | @property (nonatomic, weak) id delegate; 23 | 24 | @property (nonatomic) NSUInteger displayViewControllerIndex; //显示的viewcontroller index 25 | 26 | #warning todo 要实现setter方法 27 | @property (nonatomic, readonly) NSArray *viewControllers; 28 | 29 | 30 | @property (nonatomic, readonly) UISegmentedControl *segmentControl; 31 | 32 | @property (nonatomic, readonly) UIViewController *currentViewController; 33 | 34 | #warning todo 实现 35 | //-(void)setBadge:(NSString *)badgeVal forChildViewController:(UIViewController *)viewController; 36 | 37 | -(BOOL)switchToViewController:(UIViewController *)viewController; 38 | 39 | //for subclass 40 | -(void)willSwitchFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController; 41 | -(void)didSwitchFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController; 42 | @end 43 | 44 | @protocol SwitchableViewControllerDelegate 45 | 46 | @optional 47 | 48 | -(void)switchableView:(SwitchableViewController *)switchabeViewController didSwitchToViewController:(UIViewController *)viewController atIndex:(NSInteger)index; 49 | 50 | -(BOOL)switchableView:(SwitchableViewController *)switchabeViewController shouldSwitchToViewController:(UIViewController *)viewController atIndex:(NSInteger)index; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/SwitchableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchableViewController.m 3 | // 2bulu-QuanZi 4 | // 5 | // Created by 罗亮富 on 14-8-14. 6 | // Copyright (c) 2014年 Lolaage. All rights reserved. 7 | // 8 | 9 | #import "SwitchableViewController.h" 10 | 11 | 12 | @interface SwitchableViewController () 13 | @property (nonatomic, weak) UIViewController *currentVc; 14 | @end 15 | 16 | @implementation SwitchableViewController 17 | { 18 | UISegmentedControl *_segmentControl; 19 | UIView *_segSuperView; 20 | 21 | BOOL _isInTransition; 22 | } 23 | 24 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 25 | { 26 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 27 | if (self) { 28 | // Custom initialization 29 | } 30 | return self; 31 | } 32 | 33 | -(id)initWithViewControllers:(NSArray *)viewControllers 34 | { 35 | self = [super init]; 36 | _viewControllers = [viewControllers copy]; 37 | return self; 38 | } 39 | 40 | 41 | - (void)viewDidLoad 42 | { 43 | [super viewDidLoad]; 44 | NSUInteger count = _viewControllers.count; 45 | self.view.backgroundColor = [UIColor whiteColor]; 46 | NSMutableArray *items = [NSMutableArray array]; 47 | for(int i=0; i=_viewControllers.count) 65 | _displayViewControllerIndex = _viewControllers.count-1; 66 | _segmentControl.selectedSegmentIndex = _displayViewControllerIndex; 67 | UIViewController *vc = [_viewControllers objectAtIndex:_segmentControl.selectedSegmentIndex]; 68 | if(!vc.isViewLoaded) 69 | vc.view.frame = self.view.bounds; 70 | vc.view.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth; 71 | [self willSwitchFromViewController:nil toViewController:vc]; 72 | [self.view addSubview:vc.view]; 73 | _currentVc = vc; 74 | [self didSwitchFromViewController:nil toViewController:vc]; 75 | 76 | } 77 | 78 | -(void)viewDidLayoutSubviews 79 | { 80 | [super viewDidLayoutSubviews]; 81 | // _segmentControl.frame = CGRectMake(0, 0, self.view.frame.size.width*0.67, 30); 82 | 83 | CGRect frame = _segmentControl.frame; 84 | frame.size.width = self.view.frame.size.width*0.67; 85 | frame.origin.x = (_segmentControl.superview.frame.size.width - frame.size.width) * 0.5; 86 | _segmentControl.frame = frame; 87 | } 88 | 89 | 90 | -(void)setDisplayViewControllerIndex:(NSUInteger)displayViewControllerIndex 91 | { 92 | _displayViewControllerIndex = displayViewControllerIndex; 93 | _segmentControl.selectedSegmentIndex = _displayViewControllerIndex; 94 | if(self.isViewLoaded && _displayViewControllerIndex < _viewControllers.count) 95 | { 96 | UIViewController *vc = [_viewControllers objectAtIndex:_displayViewControllerIndex]; 97 | [self switchToViewController:vc]; 98 | } 99 | 100 | } 101 | 102 | 103 | -(UISegmentedControl *)segmentControl 104 | { 105 | return _segmentControl; 106 | } 107 | 108 | 109 | -(UIViewController *)currentViewController 110 | { 111 | return _currentVc; 112 | } 113 | 114 | -(BOOL)switchToViewController:(UIViewController *)viewController 115 | { 116 | if(!viewController || self.currentViewController == viewController) 117 | return NO; 118 | 119 | BOOL canSwitch = YES; 120 | if(_delegate && [_delegate respondsToSelector:@selector(switchableView:shouldSwitchToViewController:atIndex:)]) 121 | canSwitch = [_delegate switchableView:self shouldSwitchToViewController:viewController atIndex:index]; 122 | 123 | if(canSwitch) 124 | { 125 | return [self transationToViewController:viewController]; 126 | } 127 | else 128 | { 129 | _segmentControl.selectedSegmentIndex = _displayViewControllerIndex; 130 | return NO; 131 | } 132 | 133 | } 134 | 135 | -(void)willSwitchFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController; 136 | { 137 | //do nothing for subclass implement 138 | } 139 | 140 | -(void)didSwitchFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController; 141 | { 142 | self.navigationItem.leftBarButtonItems = toViewController.navigationItem.leftBarButtonItems; 143 | self.navigationItem.rightBarButtonItems = toViewController.navigationItem.rightBarButtonItems; 144 | 145 | if(_delegate && [_delegate respondsToSelector:@selector(switchableView:didSwitchToViewController:atIndex:)]) 146 | { 147 | [_delegate switchableView:self didSwitchToViewController:toViewController atIndex:_segmentControl.selectedSegmentIndex]; 148 | } 149 | } 150 | 151 | //for internal use 152 | -(BOOL)transationToViewController:(UIViewController *)toVC 153 | { 154 | if(toVC==_currentVc) 155 | return NO; 156 | 157 | if(!toVC.isViewLoaded) 158 | { 159 | toVC.view.frame = self.view.bounds; 160 | toVC.view.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth; 161 | } 162 | 163 | // //是否向左切换 164 | // BOOL isToLeft = [_viewControllers indexOfObject:_currentVc] < [_viewControllers indexOfObject:toVC]; 165 | // CGFloat cx; 166 | // if(isToLeft) 167 | // { 168 | // cx = -self.view.frame.size.width/2; 169 | // toVC.view.frame = CGRectMake(self.view.frame.size.width, 0, vcSize.width,vcSize.height); 170 | // } 171 | // else 172 | // { 173 | // cx = self.view.frame.size.width/2+self.view.frame.size.width; 174 | // toVC.view.frame = CGRectMake(-self.view.frame.size.width, 0, vcSize.width,vcSize.height); 175 | // } 176 | 177 | 178 | 179 | _isInTransition = YES; 180 | [self willSwitchFromViewController:_currentVc toViewController:toVC]; 181 | #if 1 182 | toVC.view.alpha = 0.0; 183 | // [self.view addSubview:toVC.view];//no need 184 | _displayViewControllerIndex = [_viewControllers indexOfObject:toVC]; 185 | [self transitionFromViewController:_currentVc 186 | toViewController:toVC duration:0.25 187 | options:UIViewAnimationOptionCurveLinear 188 | animations:^{ 189 | _currentVc.view.alpha = 0.0; 190 | toVC.view.alpha = 1.0; 191 | } 192 | completion:^(BOOL finished) 193 | { 194 | if(finished) 195 | { 196 | // [_currentVc.view removeFromSuperview];//no need]; 197 | UIViewController *vc = _currentVc; 198 | _currentVc = toVC; 199 | [self didSwitchFromViewController:vc toViewController:toVC]; 200 | } 201 | _isInTransition = NO; 202 | 203 | }]; 204 | #else 205 | [self.view addSubview:toVC.view]; 206 | [_currentVc.view removeFromSuperview]; 207 | _currentVc = toVC; 208 | _isInTransition = NO; 209 | #endif 210 | 211 | 212 | return YES; 213 | } 214 | 215 | -(void)willMoveToParentViewController:(UIViewController *)parent 216 | { 217 | [super willMoveToParentViewController:parent]; 218 | if(parent == nil) 219 | { 220 | [_currentVc willMoveToParentViewController:nil]; 221 | } 222 | } 223 | 224 | -(void)didMoveToParentViewController:(UIViewController *)parent 225 | { 226 | [super didMoveToParentViewController:parent]; 227 | if(parent == nil) 228 | { 229 | [_currentVc didMoveToParentViewController:nil]; 230 | } 231 | } 232 | 233 | -(BOOL)switchChildViewControllers:(UISegmentedControl *)seg 234 | { 235 | if (_isInTransition) 236 | return NO; 237 | 238 | UIViewController *toVC = [_viewControllers objectAtIndex:seg.selectedSegmentIndex]; 239 | return [self switchToViewController:toVC]; 240 | } 241 | 242 | //-(void)swipeRight:(UISwipeGestureRecognizer *)swp 243 | //{ 244 | // if(_segmentControl.selectedSegmentIndex>0) 245 | // { 246 | // _segmentControl.selectedSegmentIndex -= 1; 247 | // if(![self switchChildViewControllers:_segmentControl]) 248 | // _segmentControl.selectedSegmentIndex += 1; 249 | // } 250 | //} 251 | // 252 | //-(void)swipeLeft:(UISwipeGestureRecognizer *)swp 253 | //{ 254 | // if(_segmentControl.selectedSegmentIndex<_viewControllers.count-1) 255 | // { 256 | // _segmentControl.selectedSegmentIndex += 1; 257 | // if(![self switchChildViewControllers:_segmentControl]) 258 | // _segmentControl.selectedSegmentIndex -= 1; 259 | // } 260 | //} 261 | 262 | 263 | 264 | 265 | @end 266 | 267 | 268 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/commView/CommView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CommView.h 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface CommView : UIView 14 | 15 | @property (nonatomic,weak) UIViewController *viewController; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/commView/CommView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CommView.m 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import "CommView.h" 10 | #import "RRViewControllerExtension.h" 11 | #import "SwitchableViewController.h" 12 | #import "RRViewControllerExtension.h" 13 | #import "DynamicConfigViewController.h" 14 | #import "DEMO_ImageNaviBarViewController.h" 15 | #import "DEMO_normalMemoryLeakViewController.h" 16 | #import "StatisticsViewController.h" 17 | 18 | @implementation CommView 19 | { 20 | IBOutlet UILabel *_vcNameLabel; 21 | 22 | } 23 | 24 | 25 | - (IBAction)toNormalVc:(id)sender { 26 | [self pushToViewControllerOfClass:@"DEMO_normalMemoryLeakViewController"]; 27 | } 28 | 29 | - (IBAction)toBarHiddenVc:(id)sender { 30 | [self pushToViewControllerOfClass:@"DEMO_HiddenNaviBarViewController"]; 31 | } 32 | 33 | - (IBAction)toImageBarVc:(id)sender { 34 | [self pushToViewControllerOfClass:@"DEMO_ImageNaviBarViewController"]; 35 | } 36 | 37 | 38 | - (IBAction)toDynamicVc:(id)sender 39 | { 40 | DynamicConfigViewController *dyVc = [[DynamicConfigViewController alloc] initWithNibName:@"DynamicConfigViewController" bundle:nil]; 41 | [self.viewController.navigationController pushViewController:dyVc animated:YES]; 42 | } 43 | 44 | -(IBAction)toSwitchable:(id)sender 45 | { 46 | DEMO_normalMemoryLeakViewController *vc1 = [[DEMO_normalMemoryLeakViewController alloc] init]; 47 | DEMO_ImageNaviBarViewController *vc2 = [[DEMO_ImageNaviBarViewController alloc] init]; 48 | vc2.title = @"img"; 49 | DynamicConfigViewController *vc3 = [[DynamicConfigViewController alloc] initWithNibName:@"DynamicConfigViewController" bundle:nil]; 50 | vc3.title = @"dyn"; 51 | 52 | SwitchableViewController *svc = [[SwitchableViewController alloc] initWithViewControllers:@[vc1,vc2,vc3]]; 53 | 54 | [self.viewController.navigationController pushViewController:svc animated:YES]; 55 | } 56 | 57 | - (IBAction)toSysImagePicker:(id)sender { 58 | 59 | UIImagePickerController *impicker = [UIImagePickerController new]; 60 | impicker.modalPresentationStyle = UIModalPresentationFullScreen; 61 | [self.viewController presentViewController:impicker animated:impicker completion:^{ 62 | 63 | }]; 64 | } 65 | 66 | 67 | - (IBAction)foreceDismiss:(id)sender { 68 | [self.viewController dismissViewAnimated:YES completionBlock:^{ 69 | NSLog(@"%@ dismiss completed",NSStringFromClass([self.viewController class])); 70 | }]; 71 | } 72 | 73 | - (IBAction)showStatistics:(id)sender { 74 | StatisticsViewController *svc = [[StatisticsViewController alloc] init]; 75 | UINavigationController *nvc = [[UINavigationController alloc] initWithRootViewController:svc]; 76 | [self.viewController presentViewController:nvc animated:YES completion:nil]; 77 | } 78 | 79 | 80 | -(void)pushToViewControllerOfClass:(NSString *)classStr 81 | { 82 | Class cls = NSClassFromString(classStr); 83 | if(cls) 84 | { 85 | UIViewController *vc = [[cls alloc] init]; 86 | [self.viewController.navigationController pushViewController:vc animated:YES]; 87 | } 88 | 89 | } 90 | 91 | -(void)setViewController:(UIViewController *)viewController 92 | { 93 | _viewController = viewController; 94 | _vcNameLabel.text = [NSString stringWithFormat:@"%@ %p",NSStringFromClass([self.viewController class]),self.viewController]; 95 | } 96 | 97 | -(void)willMoveToSuperview:(UIView *)newSuperview 98 | { 99 | [super willMoveToSuperview:newSuperview]; 100 | } 101 | 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ViewControllers/commView/CommView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 28 | 39 | 50 | 61 | 72 | 83 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/ar.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/cusNavigationBar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roen-Ro/RRViewControllerExtension/46914f604e15e332a13a3b8ed7c00af78dc1ac7f/Example/RRUIViewControllerDemo/cusNavigationBar@2x.png -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/icon_apple@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roen-Ro/RRViewControllerExtension/46914f604e15e332a13a3b8ed7c00af78dc1ac7f/Example/RRUIViewControllerDemo/icon_apple@2x.png -------------------------------------------------------------------------------- /Example/RRUIViewControllerDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by 罗亮富(Roen) on 2018/9/18. 6 | // Copyright © 2018年 深圳市两步路信息技术有限公司. 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 | Copyright (c) 2009-2017 Olivier Poitrey rs@dailymotion.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [RRViewControllerExtension](https://github.com/Roen-Ro/RRViewControllerExtension) 2 | 3 | 4 | A lightweight UIViewController category extension for UINavigationBar appearance management, view controller push/pop/dismiss management, ViewController view statistics,memory leak detection and other convenient property and methods. Benefits include: 5 | 6 | - Manage `UINavigationBar` appearance gracefully 7 | - Automatic viewController memory leak detection with out any code modification. 8 | - Push/pop with completion block call back block 9 | - `UIViewController` life cycle method hook 10 | - ViewController view statistics 11 | - Other convenient properties 12 | 13 | Reference to [this demo](https://github.com/Roen-Ro/RRViewControllerExtension) on github, [中文介绍戳这里](https://www.jianshu.com/p/59aba25692fe)。 14 | 15 | ## Preview 16 | ![](https://github.com/Roen-Ro/DemoResources/blob/master/RRUIViewControllerExtensio/rrvc004.gif?raw=true) 17 | 18 | ## Usage 19 | 20 | ### `UINavigationBar` appearance management 21 | make specific `UINavigationBar` bar appearance specific for each viewcontroller staticly or dynamicly just by overriding method of your viewcontroller, which are defined in `UIViewController+RRExtension.h` 22 | 23 | ```objective-c 24 | //override any of the methods below in your viewcontroller's .m file to make specific navigation bar appearance 25 | 26 | -(BOOL)prefersNavigationBarHidden; 27 | -(BOOL)prefersNavigationBarTransparent; 28 | 29 | -(nullable UIColor *)preferredNavatationBarColor; 30 | -(nullable UIColor *)preferredNavigationItemColor; 31 | -(nullable UIImage *)preferredNavigationBarBackgroundImage; 32 | -(nullable NSDictionary *)preferredNavigationTitleTextAttributes; 33 | ``` 34 | Make `UINavigationBar` bar appearance dynamic change, call `[self updateNavigationAppearance:YES];` in your viewcontroller's .m file to force the update. 35 | A typical example: 36 | 37 | ```objective-c 38 | 39 | //typically in your UIScrollViewDelegate method 40 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView 41 | { 42 | BOOL mode; 43 | if(scrollView.contentOffset.y > 300) 44 | mode = NO; 45 | else 46 | mode = YES; 47 | 48 | if(mode != _previewMode) 49 | { 50 | _previewMode = mode; 51 | 52 | //force navigation appearance update 53 | [self updateNavigationAppearance:YES]; 54 | } 55 | } 56 | 57 | -(BOOL)prefersNavigationBarTransparent 58 | { 59 | if(_previewMode) 60 | return NO; 61 | else 62 | return YES; 63 | } 64 | 65 | -(nullable UIColor *)preferredNavigationItemColor 66 | { 67 | if(_previewMode) 68 | return [UIColor whiteColor]; 69 | else 70 | return [UIColor blackColor];; 71 | } 72 | 73 | ``` 74 | 75 | 76 | You can specify default `UINavigationBar` appearance by using `[[UINavigationBar appearance] setXXX:]` globally. 77 | 78 | ```objective-c 79 | 80 | [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:0 green:0.45 blue:0.8 alpha:1.0]]; 81 | [[UINavigationBar appearance] setTintColor:[UIColor redColor]]; 82 | NSDictionary * dict = [NSDictionary dictionaryWithObject:[UIColor yellowColor] forKey:NSForegroundColorAttributeName]; 83 | [[UINavigationBar appearance] setTitleTextAttributes:dict]; 84 | 85 | ``` 86 | 87 | You can also specify the default `UINavigationBar` appearance for each `UINavigationController` instance by setting properties defined in `UINavigationController+RRSet.h` 88 | ```objective-c 89 | 90 | // set default navigation bar appearance 91 | @property (nonatomic) BOOL defaultNavigationBarHidden; 92 | @property (nonatomic) BOOL defaultNavigationBarTransparent; 93 | 94 | @property (nonatomic,copy) UIColor *defaultNavatationBarColor; 95 | @property (nonatomic,copy) UIColor *defaultNavigationItemColor; 96 | @property (nonatomic,strong) UIImage *defaultNavigationBarBackgroundImage; 97 | @property (nonatomic,copy) NSDictionary *defaultNavigationTitleTextAttributes; 98 | 99 | ``` 100 | 101 | ### Memory leak detection 102 | to detect memory leak on runtime for viewcontrollers, all you have to do is just import the `RRViewControllerExtension` to your project. whenever a memory leak happened, there will be a alert show on your app. 103 | ![](https://github.com/Roen-Ro/DemoResources/blob/master/RRUIViewControllerExtensio/screen001.jpeg?raw=true) 104 | 105 | you can also spcify which class of `UIViewController` or more precisely on which `UIViewController` instance you want to do the memory leak detection by reference to methods below in `UIViewController+RRExtension.h` 106 | 107 | ```objective-c 108 | //Unavailable in release mode. \ 109 | in debug mode, defalut is NO for classes returned from +memoryLeakDetectionExcludedClasses method and YES for others 110 | @property (nonatomic,getter = memoryLeakDetectionEnabled) BOOL enabledMemoryLeakDetection; 111 | 112 | //read and add or remove values from the returned set to change default excluded memory detection classes 113 | +(NSMutableSet *)memoryLeakDetectionExcludedClasses; 114 | 115 | //for subclass to override 116 | -(void)didReceiveMemoryLeakWarning; 117 | 118 | ``` 119 | 120 | 121 | ### viewController life cylcle hook 122 | hook any of the `UIViewController` life cycylcle method before or after execution, for instacne if you want to track the user page viewing behavior, you just need to write code in your `AppDelgate.m` like: 123 | 124 | ```objective-c 125 | 126 | //log the user enter page behavior 127 | [UIViewController hookLifecycle:RRViewControllerLifeCycleViewWillAppear 128 | onTiming:RRMethodInsertTimingBefore 129 | withBlock:^(UIViewController * _Nonnull viewController, BOOL animated) { 130 | 131 | [MyLog logEnterPage:NSStringFromClass([viewController class])]; 132 | }]; 133 | 134 | 135 | //log the user leaving page behavior 136 | [UIViewController hookLifecycle:RRViewControllerLifeCycleViewDidDisappear 137 | onTiming:RRMethodInsertTimingAfter 138 | withBlock:^(UIViewController * _Nonnull viewController, BOOL animated) { 139 | 140 | [MyLog logLeavePage:NSStringFromClass([viewController class])]; 141 | }]; 142 | 143 | ``` 144 | 145 | ## Installation 146 | 147 | To install using [CocoaPods](https://github.com/cocoapods/cocoapods), add the following to your project Podfile: 148 | 149 | ```ruby 150 | pod 'RRViewControllerExtension' 151 | ``` 152 | and in your project file importing by: 153 | ```objective-c 154 | #import 155 | ``` 156 | 157 | 158 | Alternatively, drag and drop RRViewControllerExtension directory from [this code project](https://github.com/Roen-Ro/RRViewControllerExtension) into your Xcode project, importing files by: 159 | 160 | ```objective-c 161 | #import "RRViewControllerExtension.h" 162 | ``` 163 | 164 | ## TODO 165 | fix bug 166 | Hide navigation back arrow after reset the stack by: `-[UINavigationController setViewControllers:]` 167 | 168 | ## Author 169 | 170 | Roen (罗亮富), zxllf23@163.com 171 | 172 | ## Licenses 173 | 174 | All source code is licensed under the MIT License 175 | -------------------------------------------------------------------------------- /RRViewControllerExtension.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint RRViewControllerExtension.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "RRViewControllerExtension" 19 | s.version = "3.3.0" 20 | s.summary = "UINavigationBar appearance management, memory leak detection, convenient UIViewController property and methods." 21 | 22 | #说明:注释掉下面两项配置,在用 XCode15.0.1编译的时候,无法通过pod提交验证;并报错 "error: Cannot code sign because the target does not have an Info.plist file and one is not being generated automatically. " 23 | #但是!如果加上这两项配置,在打包后提交到App Store又会出现验证错误:"The bundle 'xxx/RRViewControllerExtension.framework' is missing plist key. The Info.plist file is missing the required key: CFBundleShortVersionString. " 24 | #解决方法是取消这两项配置,然后把 1、代码下载到本地,将pod改成本地引用,2、或者将pod源指向github 25 | # s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'GENERATE_INFOPLIST_FILE' => 'NO' } 26 | # s.user_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'GENERATE_INFOPLIST_FILE' => 'NO' } 27 | 28 | 29 | 30 | # This description is used to generate tags and improve search results. 31 | # * Think: What does it do? Why did you write it? What is the focus? 32 | # * Try to keep it short, snappy and to the point. 33 | # * Write the description between the DESC delimiters below. 34 | # * Finally, don't worry about the indent, CocoaPods strips it! 35 | s.description = <<-DESC 36 | A lightweight UIViewController category extension for UINavigationBar appearance management, view controller push/pop/dismiss management, memory leak detection and other convenient property and methods. 37 | DESC 38 | 39 | s.homepage = "https://github.com/Roen-Ro/RRViewControllerExtension" 40 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 41 | 42 | 43 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 44 | # 45 | # Licensing your code is important. See http://choosealicense.com for more info. 46 | # CocoaPods will detect a license file if there is a named LICENSE* 47 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 48 | # 49 | 50 | s.license = "MIT" 51 | 52 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 53 | # 54 | # Specify the authors of the library, with email addresses. Email addresses 55 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 56 | # accepts just a name if you'd rather not provide an email address. 57 | # 58 | # Specify a social_media_url where others can refer to, for example a twitter 59 | # profile URL. 60 | # 61 | 62 | s.author = { "罗亮富(Roen)" => "zxllf23@163.com" } 63 | # s.social_media_url = "https://twitter.com/RoenLuo" 64 | 65 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 66 | # 67 | # If this Pod runs only on iOS or OS X, then specify the platform and 68 | # the deployment target. You can optionally include the target after the platform. 69 | # 70 | 71 | s.platform = :ios, '13.0' 72 | 73 | # When using multiple platforms 74 | # s.ios.deployment_target = "5.0" 75 | # s.osx.deployment_target = "10.7" 76 | # s.watchos.deployment_target = "2.0" 77 | # s.tvos.deployment_target = "9.0" 78 | 79 | 80 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 81 | # 82 | # Specify the location from where the source should be retrieved. 83 | # Supports git, hg, bzr, svn and HTTP. 84 | # 85 | 86 | s.source = { :git => "https://github.com/Roen-Ro/RRViewControllerExtension.git", :tag => "#{s.version}" } 87 | 88 | 89 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 90 | # 91 | # CocoaPods is smart about how it includes source code. For source files 92 | # giving a folder will include any swift, h, m, mm, c & cpp files. 93 | # For header files it will include any header in the folder. 94 | # Not including the public_header_files will make all headers public. 95 | # 96 | 97 | s.source_files = "RRViewControllerExtension/**/*.{h,m}" 98 | # s.exclude_files = "Classes/Exclude" 99 | 100 | s.public_header_files = "RRViewControllerExtension/*.h" 101 | 102 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 103 | # 104 | # A list of resources included with the Pod. These are copied into the 105 | # target bundle with a build phase script. Anything else will be cleaned. 106 | # You can preserve files from being cleaned, please don't preserve 107 | # non-essential files like tests, examples and documentation. 108 | # 109 | 110 | # s.resource_bundles = { 111 | # 'RRViewControllerExtension' => ['RRViewControllerExtension/resources/*.png'] 112 | # } 113 | # s.resource = "RRViewControllerExtension/resources/**/*" 114 | # s.resources = "RRViewControllerExtension/resources/*.png" 115 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave" 116 | 117 | 118 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 119 | # 120 | # Link your library with frameworks, or libraries. Libraries do not include 121 | # the lib prefix of their name. 122 | # 123 | 124 | s.framework = "UIKit" 125 | 126 | 127 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 128 | # 129 | # If your library depends on compiler flags you can set them in the xcconfig hash 130 | # where they will only apply to your library. If you depend on other Podspecs 131 | # you can include multiple dependencies to ensure it works. 132 | 133 | s.requires_arc = true 134 | 135 | 136 | end 137 | -------------------------------------------------------------------------------- /RRViewControllerExtension/Pod/README.md: -------------------------------------------------------------------------------- 1 | # [RRViewControllerExtension](https://github.com/Roen-Ro/RRViewControllerExtension) 2 | 3 | 4 | Reference to [this demo](https://github.com/Roen-Ro/RRViewControllerExtension) on github 5 | 6 | ## Author 7 | 8 | Roen, zxllf23@163.com 9 | -------------------------------------------------------------------------------- /RRViewControllerExtension/RRViewControllerExtension.h: -------------------------------------------------------------------------------- 1 | // 2 | // RRViewControllerExtension.h 3 | // 4 | // Created by 罗亮富(Roen) zxllf23@163.com. 5 | // github:https://github.com/Roen-Ro/RRViewControllerExtension 6 | 7 | #ifndef RRViewControllerExtension_h 8 | #define RRViewControllerExtension_h 9 | 10 | #import "UIViewController+RRExtension.h" 11 | #import "UIViewController+RRStatistics.h" 12 | #import "UINavigationController+RRSet.h" 13 | 14 | #endif /* RRViewControllerExtension_h */ 15 | -------------------------------------------------------------------------------- /RRViewControllerExtension/UINavigationController+RRSet.h: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController+RRSet.h 3 | // Pods-RRUIViewControllerExtention_Example 4 | // 5 | // Created by 罗亮富(Roen). 6 | // 7 | 8 | 9 | 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | @interface UINavigationBar (RRSet) 14 | -(void)reloadBarBackgroundImage:(nullable UIImage *)img; 15 | -(void)reloadBarShadowImage:(nullable UIImage *)img; 16 | -(void)reloadBarBackgroundColor:(nullable UIColor *)color; 17 | -(void)reloadBarTitleTextAttributes:(nullable NSDictionary*)titleTextAttributes; 18 | @end 19 | 20 | typedef void (^TransitionCompletionCallBackType)(void); 21 | 22 | @interface UINavigationController (RRSet) 23 | 24 | @property (nonatomic, getter = isNavigationBarTransparent) BOOL navigationBarTransparent; 25 | 26 | // set default navigation bar appearance 27 | @property (nonatomic) BOOL defaultNavigationBarHidden; 28 | @property (nonatomic) BOOL defaultNavigationBarTransparent; 29 | 30 | @property (nonatomic,copy) UIColor *defaultNavatationBarColor; 31 | @property (nonatomic,copy) UIColor *defaultNavigationItemColor; 32 | @property (nonatomic,strong) UIImage *defaultNavigationBarBackgroundImage; 33 | @property (nonatomic,copy) NSDictionary *defaultNavigationTitleTextAttributes; 34 | 35 | // pop/push with completion block call backs 36 | - (void)pushViewController:(UIViewController *)viewController 37 | animated:(BOOL)animated 38 | completionBlock:(nullable TransitionCompletionCallBackType)completion; 39 | 40 | - (nullable UIViewController *)popViewControllerAnimated:(BOOL)animated 41 | completionBlock:(nullable TransitionCompletionCallBackType)completion; 42 | 43 | - (nullable NSArray<__kindof UIViewController *> *)popToViewController:(UIViewController *)viewController 44 | animated:(BOOL)animated 45 | completionBlock:(nullable TransitionCompletionCallBackType)completion; 46 | 47 | - (nullable NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated 48 | completionBlock:(nullable TransitionCompletionCallBackType)completion; 49 | 50 | @end 51 | 52 | 53 | 54 | @interface UINavigationItem (StatusStack) 55 | -(void)popStatus; 56 | -(void)pushStatus; 57 | 58 | @end 59 | 60 | NS_ASSUME_NONNULL_END 61 | -------------------------------------------------------------------------------- /RRViewControllerExtension/UINavigationController+RRSet.m: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController+RRSet.m 3 | // Pods-RRUIViewControllerExtention_Example 4 | // 5 | // Created by 罗亮富(Roen) on. 6 | // 7 | 8 | #import "UINavigationController+RRSet.h" 9 | #import 10 | #import "RRViewControllerExtension.h" 11 | 12 | static UIImage *sNavigationBarTransparentImage; 13 | 14 | UIImage * RR_ClearImage(void) { 15 | if(!sNavigationBarTransparentImage) { 16 | CGRect rect = CGRectMake(0, 0, 1, 1); 17 | 18 | UIGraphicsBeginImageContext(rect.size); 19 | 20 | CGContextRef context = UIGraphicsGetCurrentContext(); 21 | CGContextSetFillColorWithColor(context,[UIColor clearColor].CGColor); 22 | CGContextFillRect(context, rect); 23 | sNavigationBarTransparentImage = UIGraphicsGetImageFromCurrentImageContext(); 24 | UIGraphicsEndImageContext(); 25 | } 26 | return sNavigationBarTransparentImage; 27 | } 28 | /// https://github.com/QMUI/QMUIDemo_iOS/blob/master/QMUI/QMUIKit/QMUICore/QMUIRuntime.h 29 | ///targetSelector 是否在 targetClass 中 30 | CG_INLINE BOOL 31 | RR_HasOverrideSuperclassMethod(Class targetClass, SEL targetSelector) { 32 | Method method = class_getInstanceMethod(targetClass, targetSelector); 33 | if (!method) return NO; 34 | 35 | Method methodOfSuperclass = class_getInstanceMethod(class_getSuperclass(targetClass), targetSelector); 36 | if (!methodOfSuperclass) return YES; 37 | 38 | return method != methodOfSuperclass; 39 | } 40 | /** 41 | * 用 block 重写某个 class 的指定方法 42 | * @param targetClass 要重写的 class 43 | * @param targetSelector 要重写的 class 里的实例方法,注意如果该方法不存在于 targetClass 里,则什么都不做 44 | * @param implementationBlock 该 block 必须返回一个 block,返回的 block 将被当成 targetSelector 的新实现,所以要在内部自己处理对 super 的调用,以及对当前调用方法的 self 的 class 的保护判断(因为如果 targetClass 的 targetSelector 是继承自父类的,targetClass 内部并没有重写这个方法,则我们这个函数最终重写的其实是父类的 targetSelector,所以会产生预期之外的 class 的影响,例如 targetClass 传进来 UIButton.class,则最终可能会影响到 UIView.class),implementationBlock 的参数里第一个为你要修改的 class,也即等同于 targetClass,第二个参数为你要修改的 selector,也即等同于 targetSelector,第三个参数是一个 block,用于获取 targetSelector 原本的实现,由于 IMP 可以直接当成 C 函数调用,所以可利用它来实现“调用 super”的效果,但由于 targetSelector 的参数个数、参数类型、返回值类型,都会影响 IMP 的调用写法,所以这个调用只能由业务自己写。 45 | */ 46 | CG_INLINE BOOL 47 | RR_OverrideImplementation(Class targetClass, SEL targetSelector, id (^implementationBlock)(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void))) { 48 | Method originMethod = class_getInstanceMethod(targetClass, targetSelector); 49 | IMP imp = method_getImplementation(originMethod); 50 | BOOL hasOverride = RR_HasOverrideSuperclassMethod(targetClass, targetSelector); 51 | 52 | // 以 block 的方式达到实时获取初始方法的 IMP 的目的,从而避免先 swizzle 了 subclass 的方法,再 swizzle superclass 的方法,会发现前者调用时不会触发后者 swizzle 后的版本的 bug。 53 | IMP (^originalIMPProvider)(void) = ^IMP(void) { 54 | IMP result = NULL; 55 | if (hasOverride) { 56 | result = imp; 57 | } else { 58 | // 如果 superclass 里依然没有实现,则会返回一个 objc_msgForward 从而触发消息转发的流程 59 | Class superclass = class_getSuperclass(targetClass); 60 | result = class_getMethodImplementation(superclass, targetSelector); 61 | } 62 | 63 | // 这只是一个保底,这里要返回一个空 block 保证非 nil,才能避免用小括号语法调用 block 时 crash 64 | // 空 block 虽然没有参数列表,但在业务那边被转换成 IMP 后就算传多个参数进来也不会 crash 65 | if (!result) { 66 | result = imp_implementationWithBlock(^(id selfObject){ 67 | NSLog(([NSString stringWithFormat:@"%@", targetClass]), @"%@ 没有初始实现,%@\n%@", NSStringFromSelector(targetSelector), selfObject, [NSThread callStackSymbols]); 68 | }); 69 | } 70 | 71 | return result; 72 | }; 73 | 74 | if (hasOverride) { 75 | method_setImplementation(originMethod, imp_implementationWithBlock(implementationBlock(targetClass, targetSelector, originalIMPProvider))); 76 | } else { 77 | NSMethodSignature *methodSignature = [targetClass instanceMethodSignatureForSelector:targetSelector]; 78 | #pragma clang diagnostic push 79 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 80 | NSString *typeString = [methodSignature performSelector:NSSelectorFromString([NSString stringWithFormat:@"_%@String", @"type"])]; 81 | #pragma clang diagnostic pop 82 | const char *typeEncoding = method_getTypeEncoding(originMethod) ?: typeString.UTF8String; 83 | class_addMethod(targetClass, targetSelector, imp_implementationWithBlock(implementationBlock(targetClass, targetSelector, originalIMPProvider)), typeEncoding); 84 | } 85 | 86 | return YES; 87 | } 88 | 89 | #pragma mark - UINavigationController (_SetupProperty) 90 | UIKIT_EXTERN API_AVAILABLE(ios(15.0)) //NS_SWIFT_UI_ACTOR 91 | /// 其实这些reload方法可以考虑交换Set方法来实现 92 | @implementation UINavigationBar (_SetupProperty) 93 | 94 | static char kAssociatedObjectKey_OrginBackgroundColor_SetupProperty; 95 | -(void)setRr_OrginBackgroundColor:(UIColor*)rr_OrginBackgroundColor { 96 | objc_setAssociatedObject(self, &kAssociatedObjectKey_OrginBackgroundColor_SetupProperty, rr_OrginBackgroundColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 97 | } 98 | -(UIColor*)rr_OrginBackgroundColor { 99 | return objc_getAssociatedObject(self, &kAssociatedObjectKey_OrginBackgroundColor_SetupProperty); 100 | } 101 | static char kAssociatedObjectKey_Transparent_SetupProperty; 102 | -(void)setRr_Transparent:(BOOL)rr_Transparent { 103 | objc_setAssociatedObject(self, &kAssociatedObjectKey_Transparent_SetupProperty, @(rr_Transparent), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 104 | } 105 | -(BOOL)rr_Transparent { 106 | return [objc_getAssociatedObject(self, &kAssociatedObjectKey_Transparent_SetupProperty) boolValue]; 107 | } 108 | 109 | @end 110 | #pragma mark - UINavigationBar+RRSet 111 | @implementation UINavigationBar (RRSet) 112 | +(void)load { 113 | static dispatch_once_t onceToken; 114 | dispatch_once(&onceToken, ^{ 115 | /// - [_UIBarBackground updateBackground] 116 | if (@available(iOS 15.0, *)) { 117 | RR_OverrideImplementation(NSClassFromString(@"_UIBarBackground"), NSSelectorFromString(@"updateBackground"), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { 118 | return ^(UIView *selfObject) { 119 | 120 | // call super 121 | void (*originSelectorIMP)(id, SEL); 122 | originSelectorIMP = (void (*)(id, SEL))originalIMPProvider(); 123 | originSelectorIMP(selfObject, originCMD); 124 | 125 | if (!selfObject.superview) return; 126 | 127 | UIImageView *backgroundImageView1 = [selfObject valueForKey:@"_colorAndImageView1"]; 128 | UIImageView *backgroundImageView2 = [selfObject valueForKey:@"_colorAndImageView2"]; 129 | UIVisualEffectView *backgroundEffectView1 = [selfObject valueForKey:@"_effectView1"]; 130 | UIVisualEffectView *backgroundEffectView2 = [selfObject valueForKey:@"_effectView2"]; 131 | 132 | // iOS 14 系统默认特性是存在 backgroundImage 则不存在其他任何背景,但如果存在 barTintColor 则磨砂 view 也可以共存。 133 | // iOS 15 系统默认特性是 backgroundImage、backgroundColor、backgroundEffect 三者都可以共存,其中前两者共用 _colorAndImageView,而我们这个开关为了符合 iOS 14 的特性,仅针对 _colorAndImageView 是因为 backgroundImage 存在而出现的情况做处理。 134 | BOOL hasBackgroundImage1 = backgroundImageView1 && backgroundImageView1.superview && !backgroundImageView1.hidden && backgroundImageView1.image; 135 | BOOL hasBackgroundImage2 = backgroundImageView2 && backgroundImageView2.superview && !backgroundImageView2.hidden && backgroundImageView2.image; 136 | BOOL shouldHideEffectView = hasBackgroundImage1 || hasBackgroundImage2; 137 | if (shouldHideEffectView) { 138 | backgroundEffectView1.hidden = YES; 139 | backgroundEffectView2.hidden = YES; 140 | } else { 141 | // 把 backgroundImage 置为 nil,理应要恢复 effectView 的显示,但由于 iOS 15 里 effectView 有2个,什么时候显示哪个取决于 contentScrollView 的滚动位置,而这个位置在当前上下文里我们是无法得知的,所以先不处理了,交给系统在下一次 updateBackground 时刷新吧... 142 | } 143 | 144 | // 虽然scrollEdgeAppearance 也被设置,但系统始终都会同时显示两份 view(一份 standard 的一份 scrollEdge 的),当你的样式是不透明时没问题,但如果存在半透明,同时显示两份 view 就会导致两个半透明的效果重叠在一起,最终肉眼看到的样式和预期是不符合的,所以会强制让其中一份 view 隐藏掉。 145 | backgroundImageView2.hidden = YES; 146 | backgroundEffectView2.hidden = YES; 147 | }; 148 | }); 149 | } 150 | 151 | }); 152 | } 153 | 154 | -(void)_updateAppearanceBarActionBlock:(void (^ __nullable)(UINavigationBarAppearance *appearance))handler API_AVAILABLE(ios(15.0)) { 155 | if (!handler) return; 156 | UINavigationBarAppearance *appearance = self.standardAppearance; 157 | handler(appearance); 158 | self.standardAppearance = appearance; 159 | self.scrollEdgeAppearance = appearance; 160 | } 161 | 162 | 163 | -(void)reloadBarBackgroundImage:(nullable UIImage *)img { 164 | if (@available(iOS 15.0, *)) { 165 | [self _updateAppearanceBarActionBlock:^(UINavigationBarAppearance *appearance) { 166 | appearance.backgroundImage = img; 167 | }]; 168 | } else { 169 | [self setBackgroundImage:img forBarMetrics:UIBarMetricsDefault]; 170 | } 171 | } 172 | -(void)reloadBarShadowImage:(nullable UIImage *)img{ 173 | if (@available(iOS 15.0, *)) { 174 | [self _updateAppearanceBarActionBlock:^(UINavigationBarAppearance *appearance) { 175 | appearance.shadowImage = img; 176 | if (!img) { 177 | appearance.shadowImage = RR_ClearImage(); 178 | } 179 | }]; 180 | } else { 181 | [self setShadowImage:img]; 182 | } 183 | } 184 | -(void)reloadBarBackgroundColor:(nullable UIColor *)color{ 185 | if (@available(iOS 15.0, *)) { 186 | [self setRr_OrginBackgroundColor:color]; 187 | BOOL transparent = [self rr_Transparent]; 188 | [self _updateAppearanceBarActionBlock:^(UINavigationBarAppearance *obj) { 189 | if (transparent) { 190 | obj.backgroundColor = nil; 191 | obj.backgroundColor = nil; 192 | }else { 193 | obj.backgroundColor = color; 194 | obj.backgroundColor = color; 195 | } 196 | }]; 197 | } else { 198 | [self setBarTintColor:color]; 199 | } 200 | } 201 | -(void)reloadBarTitleTextAttributes:(nullable NSDictionary*)titleTextAttributes{ 202 | if (@available(iOS 15.0, *)) { 203 | [self _updateAppearanceBarActionBlock:^(UINavigationBarAppearance *obj) { 204 | obj.titleTextAttributes = titleTextAttributes; 205 | }]; 206 | 207 | } else { 208 | [self setTitleTextAttributes:titleTextAttributes]; 209 | } 210 | } 211 | 212 | -(void)_reloadBarTransparent:(BOOL)transparent { 213 | if (@available(iOS 15.0, *)) { 214 | [self setRr_Transparent:transparent]; 215 | 216 | [self _updateAppearanceBarActionBlock:^(UINavigationBarAppearance *obj) { 217 | if (transparent) { 218 | obj.backgroundEffect = nil; 219 | obj.backgroundEffect = nil; 220 | }else { 221 | UINavigationBarAppearance *temp = [[UINavigationBarAppearance alloc] init]; 222 | obj.backgroundEffect = temp.backgroundEffect; 223 | obj.backgroundEffect = temp.backgroundEffect; 224 | } 225 | }]; 226 | [self reloadBarBackgroundColor:[self rr_OrginBackgroundColor]]; 227 | } 228 | } 229 | @end 230 | @interface UIViewController (_RRAnimateAndCompletion) 231 | - (void)rr_animateAlongsideTransition:(void (^ __nullable)(id context))animation 232 | completion:(void (^ __nullable)(id context))completion; 233 | @end 234 | @implementation UIViewController (_RRAnimateAndCompletion) 235 | 236 | - (void)rr_animateAlongsideTransition:(void (^ __nullable)(id context))animation 237 | completion:(void (^ __nullable)(id context))completion { 238 | if (self.transitionCoordinator) { 239 | BOOL animationQueuedToRun = [self.transitionCoordinator animateAlongsideTransition:animation completion:completion]; 240 | // 某些情况下传给 animateAlongsideTransition 的 animation 不会被执行,这时候要自己手动调用一下 241 | if (!animationQueuedToRun && animation) { 242 | animation(nil); 243 | } 244 | } else { 245 | if (animation) animation(nil); 246 | if (completion) completion(nil); 247 | } 248 | } 249 | 250 | @end 251 | 252 | 253 | //static char kNavigationCompletionBlockKey; 254 | //static char kNavigationBlockBckupKey; 255 | 256 | #pragma mark - UINavigationController + RRSet 257 | @implementation UINavigationController (RRSet) 258 | 259 | /// navigationTransitionView:didEndTransition:fromView:toView: 在iOS 18上彻底不执行了 换种实现方式 260 | //+(void)initialize 261 | //{ 262 | // static dispatch_once_t onceToken; 263 | // dispatch_once(&onceToken, ^{ 264 | // Class class = [self class]; 265 | // 266 | //#pragma clang diagnostic push 267 | //#pragma clang diagnostic ignored "-Wundeclared-selector" 268 | // SEL originalSelector = @selector(navigationTransitionView:didEndTransition:fromView:toView:); 269 | // SEL swizzledSelector = @selector(mob_navigationTransitionView:didEndTransition:fromView:toView:); 270 | // method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class, swizzledSelector)); 271 | //#pragma clang diagnostic pop 272 | // 273 | // // for debug useage, to get the system selector message signature 274 | // // NSMethodSignature *sig = [class instanceMethodSignatureForSelector:originalSelector]; 275 | // // NSLog(@"NSMethodSignature for originalSelector is %@",sig); 276 | // 277 | // }); 278 | //} 279 | 280 | #pragma mark- appearance 281 | 282 | -(NSMutableDictionary *)navigationBarAppearanceDic 283 | { 284 | NSMutableDictionary *mDic = objc_getAssociatedObject(self, @selector(navigationBarAppearanceDic)); 285 | if(!mDic) 286 | { 287 | mDic = [NSMutableDictionary dictionaryWithCapacity:6]; 288 | objc_setAssociatedObject(self,@selector(navigationBarAppearanceDic), mDic, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 289 | } 290 | 291 | return mDic; 292 | } 293 | 294 | -(BOOL)defaultNavigationBarHidden 295 | { 296 | return [[self.navigationBarAppearanceDic objectForKey:@"barHidden"] boolValue]; 297 | } 298 | 299 | -(void)setDefaultNavigationBarHidden:(BOOL)hidden 300 | { 301 | [self.navigationBarAppearanceDic setObject:[NSNumber numberWithBool:hidden] forKey:@"barHidden"]; 302 | } 303 | 304 | -(BOOL)defaultNavigationBarTransparent 305 | { 306 | return [[self.navigationBarAppearanceDic objectForKey:@"transparent"] boolValue]; 307 | } 308 | 309 | -(void)setDefaultNavigationBarTransparent:(BOOL)transparent 310 | { 311 | [self.navigationBarAppearanceDic setObject:[NSNumber numberWithBool:transparent] forKey:@"transparent"]; 312 | } 313 | 314 | -(UIColor *)defaultNavatationBarColor 315 | { 316 | return [[self.navigationBarAppearanceDic objectForKey:@"barColor"] copy]; 317 | } 318 | 319 | -(void)setDefaultNavatationBarColor:(UIColor *)c 320 | { 321 | if(c) 322 | [self.navigationBarAppearanceDic setObject:[c copy] forKey:@"barColor"]; 323 | else 324 | [self.navigationBarAppearanceDic removeObjectForKey:@"barColor"]; 325 | } 326 | 327 | -(UIColor *)defaultNavigationItemColor 328 | { 329 | return [[self.navigationBarAppearanceDic objectForKey:@"ItmColor"] copy]; 330 | } 331 | 332 | -(void)setDefaultNavigationItemColor:(UIColor *)c 333 | { 334 | if(c) 335 | [self.navigationBarAppearanceDic setObject:[c copy] forKey:@"ItmColor"]; 336 | else 337 | [self.navigationBarAppearanceDic removeObjectForKey:@"ItmColor"]; 338 | } 339 | 340 | -(UIImage *)defaultNavigationBarBackgroundImage 341 | { 342 | return [self.navigationBarAppearanceDic objectForKey:@"barImage"]; 343 | } 344 | 345 | -(void)setDefaultNavigationBarBackgroundImage:(UIImage *)img 346 | { 347 | if(img) 348 | [self.navigationBarAppearanceDic setObject:img forKey:@"barImage"]; 349 | else 350 | [self.navigationBarAppearanceDic removeObjectForKey:@"barImage"]; 351 | } 352 | 353 | -(NSDictionary *)defaultNavigationTitleTextAttributes 354 | { 355 | return [[self.navigationBarAppearanceDic objectForKey:@"TitleAttr"] copy]; 356 | } 357 | 358 | -(void)setDefaultNavigationTitleTextAttributes:(NSDictionary *)attrDic 359 | { 360 | if(attrDic) 361 | [self.navigationBarAppearanceDic setObject:[attrDic copy] forKey:@"TitleAttr"]; 362 | else 363 | [self.navigationBarAppearanceDic removeObjectForKey:@"TitleAttr"]; 364 | } 365 | 366 | 367 | #pragma mark- transparent 368 | -(void)setNavigationBarTransparent:(BOOL)transparent 369 | { 370 | if(transparent == self.navigationBarTransparent) 371 | return; 372 | 373 | UIImage *img = nil; 374 | 375 | if(transparent) 376 | { 377 | if(!sNavigationBarTransparentImage) 378 | { 379 | CGRect rect = CGRectMake(0, 0, 1, 1); 380 | 381 | UIGraphicsBeginImageContext(rect.size); 382 | 383 | CGContextRef context = UIGraphicsGetCurrentContext(); 384 | CGContextSetFillColorWithColor(context,[UIColor clearColor].CGColor); 385 | CGContextFillRect(context, rect); 386 | sNavigationBarTransparentImage = UIGraphicsGetImageFromCurrentImageContext(); 387 | UIGraphicsEndImageContext(); 388 | } 389 | img = sNavigationBarTransparentImage; 390 | } 391 | [self.navigationBar _reloadBarTransparent:transparent]; 392 | [self.navigationBar reloadBarBackgroundImage:img]; 393 | [self.navigationBar reloadBarShadowImage:img]; 394 | } 395 | 396 | -(BOOL)isNavigationBarTransparent 397 | { 398 | if (@available(iOS 13.0, *)) { 399 | return [self.navigationBar rr_Transparent]; 400 | } 401 | UIImage *bgImage = [self.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault]; 402 | return [bgImage isEqual:sNavigationBarTransparentImage]; 403 | } 404 | 405 | 406 | #pragma mark- push/pop completion block 407 | 408 | // ---- back up blocks 409 | //#warning 2024.05.30 Notted: On very few devices, the -mob_navigationTransitionView:didEndTransition:fromView:toView method is not called, so a backup of all blocks is made here and the execution is delayed to ensure that the blocks will be executed.\ 410 | //在极少数设备上出现不会调用-mob_navigationTransitionView:didEndTransition:fromView:toView 这个方法,所以这里对所有的block做一份备份并延迟执行,以确保block会被执行。 411 | //-(void)backUpCompletionBlock:(nullable TransitionCompletionCallBackType)completion transitionAnimate:(BOOL)animated { 412 | // NSMutableArray *mArray = objc_getAssociatedObject(self, &kNavigationBlockBckupKey); 413 | // if(mArray == nil) { 414 | // mArray = [NSMutableArray arrayWithCapacity:4]; 415 | // objc_setAssociatedObject(self, &kNavigationBlockBckupKey, mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 416 | // } else { 417 | // 418 | // // NO NEED? 419 | //// for (TransitionCompletionCallBackType blk in mArray) { 420 | //// blk(); 421 | //// } 422 | //// [mArray removeAllObjects]; 423 | // } 424 | // 425 | // if (completion) { 426 | // 427 | // [mArray addObject:completion]; 428 | // 429 | // //注意:push/pop执行完成的时间,跟业务有关,有的时候在主线程做太多的逻辑处理,会导致这个时间更长,所以这里设置时间稍微长一点会合理,(毕竟是少数设备才会出现要延迟执行的情况,所以时间设置要大一点,否则正常设备也受影响) 430 | // NSTimeInterval second = 0.3; 431 | // if(animated) 432 | // second = 1.0; 433 | // 434 | // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(second * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 435 | // if ([mArray containsObject:completion]) { 436 | // completion(); 437 | // [mArray removeObject:completion]; 438 | // } 439 | // }); 440 | // } 441 | //} 442 | // 443 | //-(void)removeBackedUpBlock:(_Nullable TransitionCompletionCallBackType)completion { 444 | // if(!completion) 445 | // return; 446 | // NSMutableArray *mArray = objc_getAssociatedObject(self, &kNavigationBlockBckupKey); 447 | //#if 0 448 | // [mArray removeObject:completion]; 449 | //#else 450 | // NSUInteger idx = [mArray indexOfObject:completion]; 451 | // if(idx != NSNotFound) { 452 | // [mArray removeObjectAtIndex:idx]; 453 | // } 454 | //#endif 455 | //} 456 | // 457 | //-(void)setCompletionBlock:(nullable TransitionCompletionCallBackType)completion 458 | //{ 459 | // objc_setAssociatedObject(self, &kNavigationCompletionBlockKey, completion, OBJC_ASSOCIATION_COPY_NONATOMIC); 460 | //} 461 | // ---- end for back up blocks 462 | 463 | 464 | //Note: 2024.05.30 This method can't grantee to be called On very few devices devices; so i added block backup and excute later mechanism \ 465 | 在极少数设备上(跟系统版本没有关系)出现了不会调用这个方法的情况, 所以我新增了一个block的备份延迟执行的机制 466 | //-(void)mob_navigationTransitionView:(id)obj1 didEndTransition:(long)b fromView:(id)v1 toView:(id)v2 467 | //{ 468 | // [self mob_navigationTransitionView:obj1 didEndTransition:b fromView:v1 toView:v2]; 469 | // 470 | // TransitionCompletionCallBackType cmpltBlock = objc_getAssociatedObject(self, &kNavigationCompletionBlockKey); 471 | // 472 | // 473 | // if(cmpltBlock) { 474 | // [self setCompletionBlock:nil]; //reset the block before execution 475 | // [self removeBackedUpBlock:cmpltBlock]; 476 | // cmpltBlock(); 477 | // } 478 | // 479 | //} 480 | 481 | //-(void)setApplyGlobalConfig:(BOOL)applyGlobalConfig 482 | //{ 483 | // objc_setAssociatedObject(self, kNavigationControllerApplyGlobalConfigKey, [NSNumber numberWithBool:applyGlobalConfig], OBJC_ASSOCIATION_COPY_NONATOMIC); 484 | //} 485 | // 486 | //-(BOOL)applyGlobalConfig 487 | //{ 488 | // NSNumber *boolNum = objc_getAssociatedObject(self, kNavigationControllerApplyGlobalConfigKey); 489 | // return boolNum.boolValue; 490 | //} 491 | 492 | - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated completionBlock:(void (^ __nullable)(void))completion 493 | { 494 | // [self setCompletionBlock:completion]; 495 | // 要先进行转场操作才能产生 self.transitionCoordinator,然后才能用 rr_animateAlongsideTransition:completion:,所以不能把转场操作放在 animation block 里。 496 | [self pushViewController:viewController animated:animated]; 497 | if (completion) { 498 | [self rr_animateAlongsideTransition:nil completion:^(id _Nonnull context) { 499 | completion(); 500 | }]; 501 | } 502 | } 503 | 504 | - (nullable UIViewController *)popViewControllerAnimated:(BOOL)animated completionBlock:(void (^ __nullable)(void))completion 505 | { 506 | // [self setCompletionBlock:completion]; 507 | // return [self popViewControllerAnimated:animated]; 508 | // 要先进行转场操作才能产生 self.transitionCoordinator,然后才能用 rr_animateAlongsideTransition:completion:,所以不能把转场操作放在 animation block 里。 509 | UIViewController *result = [self popViewControllerAnimated:animated]; 510 | if (completion) { 511 | [self rr_animateAlongsideTransition:nil completion:^(id _Nonnull context) { 512 | completion(); 513 | }]; 514 | } 515 | return result; 516 | } 517 | 518 | - (nullable NSArray<__kindof UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated completionBlock:(void (^ __nullable)(void))completion 519 | { 520 | // [self setCompletionBlock:completion]; 521 | // return [self popToViewController:viewController animated:animated]; 522 | // 要先进行转场操作才能产生 self.transitionCoordinator,然后才能用 rr_animateAlongsideTransition:completion:,所以不能把转场操作放在 animation block 里。 523 | NSArray *result = [self popToViewController:viewController animated:animated]; 524 | if (completion) { 525 | [self rr_animateAlongsideTransition:nil completion:^(id _Nonnull context) { 526 | completion(); 527 | }]; 528 | } 529 | return result; 530 | } 531 | 532 | - (nullable NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated completionBlock:(void (^ __nullable)(void))completion 533 | { 534 | // [self setCompletionBlock:completion]; 535 | // return [self popToRootViewControllerAnimated:animated]; 536 | // 要先进行转场操作才能产生 self.transitionCoordinator,然后才能用 rr_animateAlongsideTransition:completion:,所以不能把转场操作放在 animation block 里。 537 | NSArray *result = [self popToRootViewControllerAnimated:animated]; 538 | if (completion) { 539 | [self rr_animateAlongsideTransition:nil completion:^(id _Nonnull context) { 540 | completion(); 541 | }]; 542 | } 543 | return result; 544 | } 545 | 546 | 547 | 548 | 549 | @end 550 | 551 | const char naviagionItemStackKey = 'a'; 552 | 553 | @implementation UINavigationItem (StatusStack) 554 | 555 | -(NSMutableArray *)statusStack 556 | { 557 | NSMutableArray *stack = objc_getAssociatedObject(self, &naviagionItemStackKey); 558 | if(!stack) 559 | { 560 | stack = [NSMutableArray arrayWithCapacity:3]; 561 | objc_setAssociatedObject(self, &naviagionItemStackKey, stack, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 562 | } 563 | 564 | return stack; 565 | } 566 | 567 | -(void)popStatus 568 | { 569 | NSMutableDictionary *mdic = [[self statusStack] lastObject]; 570 | if(mdic) 571 | { 572 | self.rightBarButtonItems = [mdic objectForKey:@"rightBarButtonItems"]; 573 | self.leftBarButtonItems = [mdic objectForKey:@"leftBarButtonItems"]; 574 | self.backBarButtonItem = [mdic objectForKey:@"backBarButtonItem"]; 575 | self.titleView = [mdic objectForKey:@"titleView"]; 576 | self.title = [mdic objectForKey:@"title"]; 577 | 578 | [[self statusStack] removeObject:mdic]; 579 | } 580 | } 581 | 582 | -(void)pushStatus 583 | { 584 | NSMutableDictionary *mdic = [NSMutableDictionary dictionaryWithCapacity:5]; 585 | 586 | if(self.rightBarButtonItems) 587 | [mdic setObject:self.rightBarButtonItems forKey:@"rightBarButtonItems"]; 588 | if(self.leftBarButtonItems) 589 | [mdic setObject:self.leftBarButtonItems forKey:@"leftBarButtonItems"]; 590 | if(self.backBarButtonItem) 591 | [mdic setObject:self.backBarButtonItem forKey:@"backBarButtonItem"]; 592 | if(self.titleView) 593 | [mdic setObject:self.titleView forKey:@"titleView"]; 594 | if(self.title) 595 | [mdic setObject:self.title forKey:@"title"]; 596 | 597 | [[self statusStack] addObject:mdic]; 598 | } 599 | 600 | @end 601 | -------------------------------------------------------------------------------- /RRViewControllerExtension/UIViewController+RRExtension.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+RRExtension.h 3 | // created by Roen(罗亮富) On 2015.07 (zxllf23@163.com) 4 | // github: https://github.com/Roen-Ro/RRViewControllerExtension 5 | 6 | #import 7 | #import "UINavigationController+RRSet.h" 8 | #import "UIViewController+RRStatistics.h" 9 | 10 | typedef enum { 11 | 12 | RRMethodInsertTimingBefore = 0, 13 | RRMethodInsertTimingAfter 14 | 15 | }RRMethodInsertTiming; 16 | 17 | 18 | typedef enum { 19 | 20 | RRViewControllerLifeCycleLoadView = 0, 21 | RRViewControllerLifeCycleViewDidLoad, 22 | RRViewControllerLifeCycleViewWillAppear, 23 | RRViewControllerLifeCycleViewDidAppear, 24 | RRViewControllerLifeCycleViewWillDisappear, 25 | RRViewControllerLifeCycleViewDidDisappear, 26 | 27 | }RRViewControllerLifeCycleMethod; 28 | 29 | typedef NS_OPTIONS(NSUInteger, RRViewControllerVisibleState) { 30 | RRViewControllerUnknow = 1 << 0, // 初始化完成但尚未触发 viewDidLoad 31 | RRViewControllerViewDidLoad = 1 << 1, // 触发了 viewDidLoad 32 | RRViewControllerWillAppear = 1 << 2, // 触发了 viewWillAppear 33 | RRViewControllerDidAppear = 1 << 3, // 触发了 viewDidAppear 34 | RRViewControllerWillDisappear = 1 << 4, // 触发了 viewWillDisappear 35 | RRViewControllerDidDisappear = 1 << 5, // 触发了 viewDidDisappear 36 | 37 | RRViewControllerVisible = RRViewControllerWillAppear | RRViewControllerDidAppear,// 表示是否处于可视范围,判断时请用 & 运算,例如 rr_visibleState & RRViewControllerVisible 38 | }; 39 | 40 | //do not enable this macro in release mode 41 | #if DEBUG 42 | #define VC_MemoryLeakDetectionEnabled 1 43 | #endif 44 | 45 | NS_ASSUME_NONNULL_BEGIN 46 | 47 | typedef void (^RRViewControllerLifecycleHookBlock) (UIViewController *viewController, BOOL animated); 48 | 49 | @interface UIViewController (RRExtension) 50 | 51 | @property (nonatomic,readonly) BOOL isViewAppearing; 52 | 53 | /** 54 | 获取当前 viewController 所处的的生命周期阶段(也即 viewDidLoad/viewWillApear/viewDidAppear/viewWillDisappear/viewDidDisappear) 55 | PS 在原有方法调用结束后才会赋值 56 | */ 57 | @property (nonatomic, readonly) RRViewControllerVisibleState rr_visibleState; 58 | #pragma mark- UINavigation related 59 | /*----------------methods below are for sublclass to override ------------*/ 60 | 61 | //return YES to hide navigationBar and NO ro display navigationBar 62 | -(BOOL)prefersNavigationBarHidden; 63 | 64 | //default returns NO, you can override this method to return different values according to the current status of the viewcontroller. 65 | -(BOOL)prefersNavigationBarTransparent; 66 | 67 | -(nullable UIColor *)preferredNavatationBarColor; 68 | -(nullable UIColor *)preferredNavigationItemColor; 69 | -(nullable UIImage *)preferredNavigationBarBackgroundImage; 70 | -(nullable NSDictionary *)preferredNavigationTitleTextAttributes; 71 | 72 | /* 73 | this method is invoked with user interaction with the navigation back item to pop back or dismiss the view controller, return YES to continue the dismiss process, return NO to block the dismission. 74 | the typicall use of this method is for an editable view controller, when user taaped on the back bottonItem, you can show alert the user to double check if the user really want to leave the page. if the user selected the "YES" option for the alert, you should directly call any of those method -dismissView,-dismissViewWithCompletionBlock:,-dismissViewAnimated:completionBlock:. 75 | */ 76 | -(BOOL)viewControllerShouldDismiss; 77 | 78 | //defaults NO for navigation root view controller and YES for others 79 | -(BOOL)navigationControllerAllowSidePanPopBack; 80 | 81 | /*--------------------------------------------------*/ 82 | 83 | #pragma mark- 84 | 85 | // force update,call this method whenever the return values for the view controller's navigation appearance methods should change. 86 | -(void)updateNavigationAppearance:(BOOL)animated; 87 | 88 | //show/hide the navigation back buttonItem on left of the navigation bar 89 | -(void)showNavigationBackItem:(BOOL)show; 90 | 91 | //push current navigation item 92 | -(void)navitationItemPush; 93 | //pop the last pushed navigation item 94 | -(void)navitationItemPop; 95 | 96 | 97 | #pragma mark- dismiss 98 | //pop/dismiss viewcontroller methods 99 | - (void)dismissView; 100 | - (void)dismissViewWithCompletionBlock: (void (^ __nullable)(void))completion; 101 | - (void)dismissViewAnimated:(BOOL)animate completionBlock:(void (^ __nullable)(void))completion; 102 | 103 | #pragma mark- memory leak detection 104 | 105 | //Unavailable in release mode. \ 106 | in debug mode, defalut is NO for classes returned from +memoryLeakDetectionExcludedClasses method and YES for others 107 | @property (nonatomic,getter = memoryLeakDetectionEnabled) BOOL enabledMemoryLeakDetection; 108 | 109 | //read and add or remove values from the returned set to change default excluded memory detection classes 110 | +(NSMutableSet *)memoryLeakDetectionExcludedClasses; 111 | 112 | //for subclass to override 113 | -(void)didReceiveMemoryLeakWarning; 114 | 115 | #pragma mark- global setting 116 | //customize the navigation bar back button item image 117 | +(UIImage *)navigationBackBarButtonItemImage; 118 | +(void)setNavigationBackBarButtonItemImage:(UIImage *)image; 119 | 120 | #pragma mark- hook 121 | 122 | /* 123 | Adds a block of code before/after the `lifecycleMethod` 124 | @param lifecycleMethod the lifecycle method being hooked. 125 | @param timing before or after the life cylce method 126 | @param block the block code to implement the hook 127 | 128 | NOTE:the newlly set hook blocks will take place of the older ones with the same lifecycle method and same timing. 129 | Or you can use Aspects(https://github.com/steipete/Aspects) to add hook for any objc method 130 | */ 131 | +(void)hookLifecycle:(RRViewControllerLifeCycleMethod)lifecycleMethod 132 | onTiming:(RRMethodInsertTiming)timing 133 | withBlock:(RRViewControllerLifecycleHookBlock)block; 134 | 135 | 136 | 137 | /** 138 | Debug purpose class methods 139 | */ 140 | +(NSString *)logStringFor:(UIViewController *)viewController;// 141 | +(NSString *)stackStringFor:(NSArray *)viewControllers; 142 | 143 | @end 144 | 145 | NS_ASSUME_NONNULL_END 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /RRViewControllerExtension/UIViewController+RRExtension.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+RRExtension.m 3 | // Roen(罗亮富)zxllf23@163.com 2015.07 4 | 5 | #import "UIViewController+RRExtension.h" 6 | #import 7 | 8 | 9 | //RRStatisticBridge只是用来 10 | @interface UIViewController (RRStatisticBridge) 11 | //只在viewWillDisappear:方法中调用 12 | +(void)staticviewWillDisappearForViewController:(UIViewController *)viewController; 13 | //只在-viewDidAppear:方法中调用 14 | +(void)staticviewDidAppearForViewController:(UIViewController *)viewController; 15 | @end 16 | 17 | 18 | static UIImage *backIndicatorImage; 19 | static NSMutableDictionary *sBeforeHookBlockMap; 20 | static NSMutableDictionary *sAfterHookBlockMap; 21 | #if VC_MemoryLeakDetectionEnabled 22 | static NSHashTable *sVcLeakDetectionHashTable; 23 | static NSMutableSet *sVcLeakDetectionDefaultExceptions; 24 | __weak UIView *sMemleakWarningView; 25 | #endif 26 | 27 | 28 | @implementation UIViewController (RRExtension) 29 | 30 | //2022.09.14 move from +(void)initialize since ios16 31 | + (void)load 32 | { 33 | static dispatch_once_t onceToken; 34 | dispatch_once(&onceToken, ^{ 35 | 36 | Class class = [self class]; 37 | 38 | SEL originalSelector = @selector(loadView); 39 | SEL swizzledSelector = @selector(exchg_loadView); 40 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class, swizzledSelector)); 41 | 42 | originalSelector = @selector(viewDidLoad); 43 | swizzledSelector = @selector(exchg_viewDidLoad); 44 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class, swizzledSelector)); 45 | 46 | originalSelector = @selector(viewWillAppear:); 47 | swizzledSelector = @selector(exchg_viewWillAppear:); 48 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class,swizzledSelector)); 49 | 50 | originalSelector = @selector(viewDidAppear:); 51 | swizzledSelector = @selector(exchg_viewDidAppear:); 52 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class,swizzledSelector)); 53 | 54 | originalSelector = @selector(viewWillDisappear:); 55 | swizzledSelector = @selector(exchg_viewWillDisappear:); 56 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class,swizzledSelector)); 57 | 58 | originalSelector = @selector(viewDidDisappear:); 59 | swizzledSelector = @selector(exchg_viewDidDisappear:); 60 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class,swizzledSelector)); 61 | 62 | 63 | originalSelector = @selector(willMoveToParentViewController:); 64 | swizzledSelector = @selector(exchg_willMoveToParentViewController:); 65 | method_exchangeImplementations(class_getInstanceMethod(class, originalSelector), class_getInstanceMethod(class,swizzledSelector)); 66 | 67 | #if VC_MemoryLeakDetectionEnabled 68 | sVcLeakDetectionHashTable = [NSHashTable weakObjectsHashTable]; 69 | sVcLeakDetectionDefaultExceptions = [NSMutableSet setWithObjects:@"UIAlertController", 70 | @"_UIRemoteInputViewController", 71 | @"UICompatibilityInputViewController", 72 | @"SFAirDropViewController", 73 | @"UISystemInputAssistantViewController", 74 | nil]; 75 | #endif 76 | 77 | 78 | }); 79 | } 80 | 81 | #pragma mark- hook 82 | +(void)hookLifecycle:(RRViewControllerLifeCycleMethod)lifecycleMethod 83 | onTiming:(RRMethodInsertTiming)timing 84 | withBlock:(RRViewControllerLifecycleHookBlock)block 85 | { 86 | if(block) 87 | { 88 | if(timing == RRMethodInsertTimingBefore) 89 | { 90 | if(!sBeforeHookBlockMap) 91 | sBeforeHookBlockMap = [NSMutableDictionary dictionary]; 92 | 93 | [sBeforeHookBlockMap setObject:block forKey:@(lifecycleMethod)]; 94 | } 95 | else if(timing == RRMethodInsertTimingAfter) 96 | { 97 | if(!sAfterHookBlockMap) 98 | sAfterHookBlockMap = [NSMutableDictionary dictionary]; 99 | 100 | [sAfterHookBlockMap setObject:block forKey:@(lifecycleMethod)]; 101 | } 102 | } 103 | } 104 | 105 | -(void)invokeBeforeHookForLifecycle:(RRViewControllerLifeCycleMethod)lifecycleMethod animated:(BOOL)animate 106 | { 107 | RRViewControllerLifecycleHookBlock blk = [sBeforeHookBlockMap objectForKey:@(lifecycleMethod)]; 108 | if(blk) 109 | blk(self,animate); 110 | } 111 | 112 | -(void)invokeAfterHookForLifecycle:(RRViewControllerLifeCycleMethod)lifecycleMethod animated:(BOOL)animate 113 | { 114 | RRViewControllerLifecycleHookBlock blk = [sAfterHookBlockMap objectForKey:@(lifecycleMethod)]; 115 | if(blk) 116 | blk(self,animate); 117 | } 118 | 119 | #pragma mark- global setting 120 | +(UIImage *)navigationBackBarButtonItemImage 121 | { 122 | if(!backIndicatorImage) 123 | { 124 | CGRect rect = CGRectMake(0, 0, 24, 44); 125 | CGFloat yInset = 14; 126 | CGFloat xOrg = 1; 127 | CGFloat xLen = 8; 128 | UIGraphicsBeginImageContextWithOptions(rect.size, NO, [UIScreen mainScreen].scale); 129 | CGContextRef context = UIGraphicsGetCurrentContext(); 130 | CGContextSetStrokeColorWithColor(context,[UIColor blackColor].CGColor); 131 | CGContextSetLineJoin(context, kCGLineJoinRound); 132 | CGContextSetLineCap(context, kCGLineCapRound); 133 | CGContextSetLineWidth(context, 2.0); 134 | CGMutablePathRef path = CGPathCreateMutable(); 135 | CGPathMoveToPoint(path, NULL, xOrg+xLen, yInset); 136 | CGPathAddLineToPoint(path, NULL, xOrg, rect.size.height/2); 137 | CGPathAddLineToPoint(path, NULL, xOrg+xLen, rect.size.height-yInset); 138 | CGContextAddPath(context, path); 139 | CGContextStrokePath(context); 140 | backIndicatorImage = UIGraphicsGetImageFromCurrentImageContext(); 141 | UIGraphicsEndImageContext(); 142 | CGPathRelease(path); 143 | 144 | if (@available(iOS 10.0, *)) { 145 | UIView *v = [UIView new]; 146 | if(v.effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) 147 | backIndicatorImage = [UIImage imageWithCGImage:backIndicatorImage.CGImage scale:backIndicatorImage.scale orientation:UIImageOrientationDown ]; 148 | } 149 | 150 | } 151 | return backIndicatorImage; 152 | } 153 | +(void)setNavigationBackBarButtonItemImage:(UIImage *)image 154 | { 155 | backIndicatorImage = image; 156 | } 157 | 158 | #pragma mark - exchanged life cyle methods 159 | 160 | -(void)exchg_loadView 161 | { 162 | [self invokeBeforeHookForLifecycle:RRViewControllerLifeCycleLoadView animated:NO]; 163 | 164 | [self exchg_loadView]; 165 | 166 | //有时候会在loadView中设置navigationItem,用户改变了这些设置的话,这里就不做其他处理了 167 | if([self shouldShowBackItem]) 168 | [self showNavigationBackItem:YES]; 169 | 170 | [self invokeAfterHookForLifecycle:RRViewControllerLifeCycleLoadView animated:NO]; 171 | } 172 | 173 | - (void)exchg_viewDidLoad 174 | { 175 | [self invokeBeforeHookForLifecycle:RRViewControllerLifeCycleViewDidLoad animated:NO]; 176 | 177 | [self exchg_viewDidLoad]; 178 | 179 | self.rr_visibleState = RRViewControllerViewDidLoad; 180 | 181 | //发现有些vc并不会调用exchg_loadView方法,所以这里再加一步保障 182 | if([self shouldShowBackItem]) 183 | [self showNavigationBackItem:YES]; 184 | 185 | [self invokeAfterHookForLifecycle:RRViewControllerLifeCycleViewDidLoad animated:NO]; 186 | 187 | } 188 | 189 | - (void)exchg_viewWillAppear:(BOOL)animated 190 | { 191 | [self invokeBeforeHookForLifecycle:RRViewControllerLifeCycleViewWillAppear animated:animated]; 192 | 193 | [self exchg_viewWillAppear:animated]; 194 | 195 | self.rr_visibleState = RRViewControllerWillAppear; 196 | 197 | [self updateNavigationAppearance:animated]; 198 | self.navigationController.interactivePopGestureRecognizer.delegate = self; 199 | // self.navigationController.interactivePopGestureRecognizer.enabled = YES; 200 | 201 | [self invokeAfterHookForLifecycle:RRViewControllerLifeCycleViewWillAppear animated:animated]; 202 | } 203 | 204 | -(void)exchg_viewDidAppear:(BOOL)animated 205 | { 206 | [self invokeBeforeHookForLifecycle:RRViewControllerLifeCycleViewDidAppear animated:animated]; 207 | 208 | [self exchg_viewDidAppear:animated]; 209 | 210 | self.rr_visibleState = RRViewControllerDidAppear; 211 | 212 | [UIViewController staticviewDidAppearForViewController:self]; 213 | 214 | // objc_setAssociatedObject(self, @"viewAppear", @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 215 | 216 | [self setNeedsStatusBarAppearanceUpdate]; 217 | 218 | [self invokeAfterHookForLifecycle:RRViewControllerLifeCycleViewDidAppear animated:animated]; 219 | } 220 | 221 | - (void)exchg_viewWillDisappear:(BOOL)animated 222 | { 223 | [self invokeBeforeHookForLifecycle:RRViewControllerLifeCycleViewWillDisappear animated:animated]; 224 | 225 | [self exchg_viewWillDisappear:animated]; 226 | 227 | self.rr_visibleState = RRViewControllerWillDisappear; 228 | 229 | [UIViewController staticviewWillDisappearForViewController:self]; 230 | 231 | [self invokeAfterHookForLifecycle:RRViewControllerLifeCycleViewWillDisappear animated:animated]; 232 | 233 | } 234 | 235 | -(void)exchg_viewDidDisappear:(BOOL)animated 236 | { 237 | [self invokeBeforeHookForLifecycle:RRViewControllerLifeCycleViewDidDisappear animated:animated]; 238 | [self exchg_viewDidDisappear:animated]; 239 | 240 | self.rr_visibleState = RRViewControllerDidDisappear; 241 | 242 | // objc_setAssociatedObject(self, @"viewAppear", @NO, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 243 | [self invokeAfterHookForLifecycle:RRViewControllerLifeCycleViewDidDisappear animated:animated]; 244 | 245 | #if VC_MemoryLeakDetectionEnabled 246 | if(self.memoryLeakDetectionEnabled) 247 | { 248 | [sVcLeakDetectionHashTable addObject:self]; 249 | [self detectMemoryLeak]; 250 | } 251 | #endif 252 | } 253 | 254 | -(void)exchg_willMoveToParentViewController:(UIViewController *)parent { 255 | [self exchg_willMoveToParentViewController:parent]; 256 | } 257 | #pragma mark- extended properties 258 | -(BOOL)isViewAppearing 259 | { 260 | // NSNumber *v = objc_getAssociatedObject(self, @"viewAppear"); 261 | // return v.boolValue; 262 | RRViewControllerVisibleState state = self.rr_visibleState; 263 | return state >= RRViewControllerDidAppear && state <= RRViewControllerWillDisappear; 264 | } 265 | 266 | 267 | static char kAssociatedObjectKey_visibleState; 268 | -(void)setRr_visibleState:(RRViewControllerVisibleState)rr_visibleState { 269 | objc_setAssociatedObject(self, &kAssociatedObjectKey_visibleState, @(rr_visibleState), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 270 | } 271 | -(RRViewControllerVisibleState)rr_visibleState { 272 | return [((NSNumber *)objc_getAssociatedObject(self, &kAssociatedObjectKey_visibleState)) unsignedIntegerValue]; 273 | } 274 | 275 | 276 | #pragma mark- 277 | 278 | //only adapt for calling in method -loadView 279 | -(BOOL)shouldShowBackItem { 280 | 281 | return ( self.navigationController 282 | && !self.navigationItem.leftBarButtonItems 283 | && ![self inter_prefersNavigationBackItemHidden] 284 | ); 285 | } 286 | 287 | -(void)showNavigationBackItem:(BOOL)show 288 | { 289 | NSMutableArray *leftItems = [NSMutableArray arrayWithArray:self.navigationItem.leftBarButtonItems]; 290 | UIBarButtonItem *backItem = self.navigationBackItem; 291 | 292 | if(!backItem) 293 | return; 294 | 295 | if(show) 296 | { 297 | if(![leftItems containsObject:backItem]) 298 | { 299 | [leftItems insertObject:backItem atIndex:0]; 300 | } 301 | } 302 | else if([leftItems containsObject:backItem]) 303 | { 304 | [leftItems removeObject:backItem]; 305 | } 306 | 307 | 308 | self.navigationItem.leftBarButtonItems = leftItems; 309 | } 310 | 311 | -(void)updateNavigationAppearance:(BOOL)animated 312 | { 313 | if(!self.navigationController || !self.isViewLoaded) 314 | return; 315 | 316 | if([self.navigationController isKindOfClass:[UIImagePickerController class]]) 317 | return; 318 | 319 | BOOL hidden = [self prefersNavigationBarHidden]; 320 | 321 | if(hidden) 322 | { 323 | [self.navigationController setNavigationBarHidden:YES animated:animated]; 324 | } 325 | else 326 | { 327 | #pragma clang diagnostic push 328 | #pragma clang diagnostic ignored "-Wdeprecated" 329 | if (!self.searchDisplayController.isActive) 330 | { 331 | [self.navigationController setNavigationBarHidden:NO animated:animated]; 332 | } 333 | #pragma clang diagnostic pop 334 | } 335 | 336 | //only set in viewDidLoad 337 | // [self showNavigationBackItem:![self inter_prefersNavigationBackItemHidden]]; 338 | 339 | BOOL transparent = [self prefersNavigationBarTransparent]; 340 | [self.navigationController setNavigationBarTransparent:transparent]; 341 | if(!transparent) 342 | { 343 | //#warning 这里是否欠妥,如果设置为nil的话,是不是把上次设定的默认图片也覆盖了 344 | //set navigation bar background image 345 | UIImage *bgImage = [self preferredNavigationBarBackgroundImage]; 346 | [self.navigationController.navigationBar reloadBarBackgroundImage:bgImage]; 347 | [self.navigationController.navigationBar reloadBarShadowImage:nil]; 348 | } 349 | 350 | 351 | //set navigation bar tintColor 352 | [self.navigationController.navigationBar reloadBarBackgroundColor:[self preferredNavatationBarColor]]; 353 | 354 | //set navigation bar item tintColor 355 | UIColor *barItemTintColor = [self preferredNavigationItemColor]; 356 | [self.navigationController.navigationBar setTintColor:barItemTintColor]; 357 | 358 | //set navigation bar title attributed 359 | [self.navigationController.navigationBar reloadBarTitleTextAttributes:[self preferredNavigationTitleTextAttributes]]; 360 | 361 | } 362 | 363 | 364 | -(UIBarButtonItem *)navigationBackItem 365 | { 366 | UIBarButtonItem *backItem = objc_getAssociatedObject(self, @"backItem"); 367 | if(!backItem) 368 | { 369 | backItem = [[UIBarButtonItem alloc] initWithImage:[[self class] navigationBackBarButtonItemImage] style:UIBarButtonItemStylePlain target:nil action:nil]; 370 | objc_setAssociatedObject(self, @"backItem", backItem, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 371 | 372 | backItem.target = self; 373 | backItem.action = @selector(dismissBarButtonItemEventHandle:); 374 | } 375 | 376 | return backItem; 377 | } 378 | 379 | -(UIColor *)preferredNavatationBarColor 380 | { 381 | UIColor *c = self.navigationController.defaultNavatationBarColor; 382 | if(!c) 383 | c = [UINavigationBar appearance].barTintColor; 384 | 385 | return c; 386 | } 387 | 388 | -(UIColor *)preferredNavigationItemColor 389 | { 390 | UIColor *c = self.navigationController.defaultNavigationItemColor; 391 | if(!c) 392 | c = [UINavigationBar appearance].tintColor; 393 | 394 | return c; 395 | } 396 | 397 | -(NSDictionary *)preferredNavigationTitleTextAttributes 398 | { 399 | NSDictionary *dic = self.navigationController.defaultNavigationTitleTextAttributes; 400 | 401 | if(!dic) 402 | dic = [[UINavigationBar appearance] titleTextAttributes]; 403 | 404 | return dic; 405 | } 406 | 407 | -(UIImage *)preferredNavigationBarBackgroundImage 408 | { 409 | UIImage *img = self.navigationController.defaultNavigationBarBackgroundImage; 410 | 411 | if(!img) 412 | img = [[UINavigationBar appearance] backgroundImageForBarMetrics:UIBarMetricsDefault]; 413 | 414 | return img; 415 | } 416 | 417 | -(BOOL)prefersNavigationBarTransparent 418 | { 419 | return self.navigationController.defaultNavigationBarTransparent; 420 | } 421 | 422 | 423 | -(BOOL)prefersNavigationBarHidden 424 | { 425 | return self.navigationController.defaultNavigationBarHidden; 426 | } 427 | 428 | -(BOOL)inter_prefersNavigationBackItemHidden 429 | { 430 | BOOL hidden = NO; 431 | if(!self.navigationController.presentingViewController) 432 | { 433 | if(self.navigationController.childViewControllers.count > 0) 434 | { 435 | if(self.navigationController.viewControllers.firstObject == self) 436 | hidden = YES; 437 | } 438 | } 439 | 440 | return hidden; 441 | } 442 | 443 | 444 | -(BOOL)viewControllerShouldDismiss 445 | { 446 | return YES; 447 | } 448 | 449 | -(void)navitationItemPush 450 | { 451 | [self.navigationItem popStatus]; 452 | } 453 | -(void)navitationItemPop 454 | { 455 | [self.navigationItem pushStatus]; 456 | } 457 | 458 | #pragma mark- 459 | 460 | -(IBAction)dismissBarButtonItemEventHandle:(UIBarButtonItem *)backItem 461 | { 462 | if([self viewControllerShouldDismiss]) 463 | [self dismissView]; 464 | } 465 | 466 | 467 | -(void)dismissView 468 | { 469 | [self dismissViewWithCompletionBlock:nil]; 470 | } 471 | 472 | - (void)dismissViewWithCompletionBlock: (void (^ __nullable)(void))completion 473 | { 474 | [self dismissViewAnimated:YES completionBlock:completion]; 475 | 476 | } 477 | 478 | - (void)dismissViewAnimated:(BOOL)animate completionBlock: (void (^ __nullable)(void))completion 479 | { 480 | __weak typeof(self) weak_self = self; 481 | UIViewController *popBackVc = nil; 482 | if(self.navigationController) 483 | { 484 | NSArray *viewControllers = weak_self.navigationController.viewControllers; 485 | NSUInteger selfIndx = NSNotFound;// 486 | 487 | UIViewController *tmpVc = self; 488 | 489 | //这个操作骚不骚?Coquettish operation 就这么叫吧 490 | for(int i=0; i<1000; i++) //limit loop time to avoid dead loop 491 | { 492 | selfIndx = [viewControllers indexOfObject:tmpVc]; 493 | if(selfIndx != NSNotFound) 494 | break; 495 | 496 | tmpVc = tmpVc.parentViewController; 497 | } 498 | 499 | 500 | if(selfIndx > 0 && selfIndx != NSNotFound) 501 | popBackVc = [viewControllers objectAtIndex:selfIndx-1]; 502 | } 503 | 504 | if(popBackVc) 505 | { 506 | [self.navigationController popToViewController:popBackVc animated:animate completionBlock:completion]; 507 | } 508 | else if(self.presentingViewController || self.navigationController.presentingViewController) 509 | { 510 | [self dismissViewControllerAnimated:animate completion:completion]; 511 | } 512 | } 513 | 514 | #pragma mark- Navigation pull back 515 | -(BOOL)navigationControllerAllowSidePanPopBack 516 | { 517 | if(self.navigationController.childViewControllers.count == 1)//必须增加这个判断条件 否则会阻断用户触摸事件 518 | return NO; 519 | else 520 | { 521 | return [self viewControllerShouldDismiss]; 522 | } 523 | } 524 | 525 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 526 | { 527 | if(gestureRecognizer == self.navigationController.interactivePopGestureRecognizer) 528 | { 529 | return [self navigationControllerAllowSidePanPopBack]; 530 | } 531 | else 532 | return YES; 533 | } 534 | 535 | 536 | #pragma mark- memory leak detection 537 | 538 | -(void)detectMemoryLeak 539 | { 540 | #if VC_MemoryLeakDetectionEnabled 541 | if(self == [UIApplication sharedApplication].keyWindow.rootViewController) 542 | return; 543 | 544 | __weak typeof(self) weak_self = self; 545 | 546 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 547 | 548 | if(weak_self 549 | && !weak_self.parentViewController 550 | && !weak_self.presentingViewController 551 | && !weak_self.view.superview) 552 | { 553 | __strong typeof(weak_self) strong_self = weak_self; 554 | if([sVcLeakDetectionHashTable containsObject:strong_self]) 555 | { 556 | [strong_self didReceiveMemoryLeakWarning]; 557 | } 558 | } 559 | }); 560 | 561 | #endif 562 | } 563 | 564 | -(void)didReceiveMemoryLeakWarning 565 | { 566 | #if VC_MemoryLeakDetectionEnabled 567 | NSString *info = [NSString stringWithFormat:@" %@:%@",NSStringFromClass([self class]),self.title]; 568 | 569 | UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; 570 | 571 | if(!sMemleakWarningView) 572 | { 573 | UIView *v = [[UIView alloc]initWithFrame:CGRectMake(0, keyWindow.frame.size.height - 100, keyWindow.frame.size.width, 100)]; 574 | v.alpha = 0.9; 575 | [keyWindow addSubview:v]; 576 | 577 | UITextView *txV = [[UITextView alloc]initWithFrame:v.bounds]; 578 | txV.tag = 23; 579 | txV.editable = NO; 580 | txV.backgroundColor = [UIColor redColor]; 581 | txV.textColor = [UIColor yellowColor]; 582 | txV.font = [UIFont systemFontOfSize:14]; 583 | txV.text = @"Memory leak warnings:"; 584 | [v addSubview:txV]; 585 | 586 | UIButton *clsBtn = [[UIButton alloc]initWithFrame:CGRectMake(txV.frame.size.width-50, 5, 44, 30)]; 587 | clsBtn.backgroundColor = [UIColor colorWithWhite:1 alpha:0.5]; 588 | [clsBtn setTitle:@"x" forState:UIControlStateNormal]; 589 | [clsBtn setTintColor:[UIColor greenColor]]; 590 | [clsBtn addTarget:[self class] action:@selector(closeWarning) forControlEvents:UIControlEventTouchUpInside]; 591 | [v addSubview:clsBtn]; 592 | 593 | sMemleakWarningView = v; 594 | } 595 | 596 | UITextView *txV = [sMemleakWarningView viewWithTag:23]; 597 | 598 | NSMutableString *mStr = [NSMutableString stringWithString:txV.text]; 599 | [mStr appendFormat:@"\n%@",info]; 600 | txV.text = mStr; 601 | [keyWindow bringSubviewToFront:sMemleakWarningView]; 602 | NSLog(@"WARNING:Detected memory leak with %@",info); 603 | #endif 604 | } 605 | 606 | #if VC_MemoryLeakDetectionEnabled 607 | +(void)closeWarning 608 | { 609 | [sMemleakWarningView removeFromSuperview]; 610 | } 611 | #endif 612 | 613 | -(void)setEnabledMemoryLeakDetection:(BOOL)enable 614 | { 615 | #if VC_MemoryLeakDetectionEnabled 616 | objc_setAssociatedObject(self, @"memleakDetec", [NSNumber numberWithBool:enable], OBJC_ASSOCIATION_RETAIN); 617 | #endif 618 | } 619 | 620 | -(BOOL)memoryLeakDetectionEnabled 621 | { 622 | #if VC_MemoryLeakDetectionEnabled 623 | NSNumber *n = objc_getAssociatedObject(self, @"memleakDetec"); 624 | if(n) 625 | return n.boolValue; 626 | else 627 | { 628 | NSString *myClassStr = NSStringFromClass([self class]); 629 | return ![sVcLeakDetectionDefaultExceptions containsObject:myClassStr]; 630 | } 631 | #else 632 | return NO; 633 | #endif 634 | } 635 | 636 | +(NSMutableSet *)memoryLeakDetectionExcludedClasses 637 | { 638 | #if VC_MemoryLeakDetectionEnabled 639 | return sVcLeakDetectionDefaultExceptions; 640 | #else 641 | return nil; 642 | #endif 643 | } 644 | 645 | // MARK: - Other class methods 646 | +(NSString *)stackStringFor:(NSArray *)viewControllers { 647 | NSMutableString *retStr = [NSMutableString stringWithCapacity:1024]; 648 | [retStr appendString:@"[\n"]; 649 | int i = 0; 650 | for(UIViewController *vc in viewControllers) { 651 | [retStr appendString:[self logStringFor:vc]]; 652 | if(i < viewControllers.count - 1) { 653 | [retStr appendString:@"\n"]; 654 | } 655 | i++; 656 | } 657 | [retStr appendString:@"\n]"]; 658 | return retStr.copy; 659 | } 660 | 661 | 662 | +(NSString *)logStringFor:(UIViewController *)viewController { 663 | return [NSString stringWithFormat:@"%@.%@<%p>",NSStringFromClass(viewController.class), viewController.title,viewController];; 664 | } 665 | 666 | @end 667 | -------------------------------------------------------------------------------- /RRViewControllerExtension/UIViewController+RRStatistics.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+RRStatistics.h 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by luoliangfu on 2021/12/23. 6 | // Copyright © 2021 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | @interface RRViewControllerStatistic : NSObject 13 | @property (nonatomic) CFAbsoluteTime enterTime; //The time entered the viewController 14 | @property (nonatomic) NSTimeInterval stayTime; //Stay duration in the viewController for each statistic time 15 | @property (nonatomic) NSInteger viewCount; //total view count for a viewController 16 | 17 | @end 18 | 19 | 20 | 21 | @interface UIViewController (RRStatistics) 22 | 23 | //the name used for the viewcontroller's view statistics, default is the viewcontroller's class name 24 | @property (nonatomic,strong) NSString *statisticName; 25 | 26 | /** 27 | Whether the viewcontroller should be considered while doing the view statistics. 28 | default value: NO for those who have children viewcontrollers such as UINavigationController, UITabViewController etc. YES for ohters that doesn't have any children viewcontrollers 29 | */ 30 | @property (nonatomic, getter = statisticEnabled) BOOL enableStatistic; 31 | 32 | @property (nonatomic, readonly) BOOL isInModalPresenting; 33 | 34 | //Read statistics data 35 | +(NSDictionary *)rrStatisticsData; 36 | 37 | //stringified statistic data with top (stay time) records count 38 | +(NSString *)stringifyStatisticsWithTopStayedByCount:(unsigned int)topCount; 39 | 40 | //Only call this method in UIApplicationDelegate's -applicationDidBecomeActive: 41 | +(void)RRStaticAppEnterForeground; 42 | 43 | //Only call this method in UIApplicationDelegate's -applicationDidEnterBackground: 44 | +(void)RRStaticAppEnterbackground; 45 | @end 46 | 47 | 48 | 49 | 50 | NS_ASSUME_NONNULL_END 51 | -------------------------------------------------------------------------------- /RRViewControllerExtension/UIViewController+RRStatistics.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+RRStatistics.m 3 | // RRUIViewControllerDemo 4 | // 5 | // Created by luoliangfu on 2021/12/23. 6 | // Copyright © 2021 深圳市两步路信息技术有限公司. All rights reserved. 7 | // 8 | 9 | #import "UIViewController+RRStatistics.h" 10 | #import 11 | 12 | 13 | @implementation RRViewControllerStatistic 14 | -(instancetype)copyWithZone:(NSZone *)zone { 15 | RRViewControllerStatistic *s = [[[self class] alloc] init]; 16 | s.stayTime = self.stayTime; 17 | s.viewCount = self.viewCount; 18 | s.enterTime = self.enterTime; 19 | return s; 20 | } 21 | 22 | -(instancetype)initWithCoder:(NSCoder *)coder { 23 | self = [super init]; 24 | _enterTime = 0; 25 | _stayTime = [coder decodeDoubleForKey:@"stayTime"]; 26 | _viewCount = [coder decodeIntegerForKey:@"viewCount"]; 27 | return self; 28 | } 29 | 30 | -(void)encodeWithCoder:(NSCoder *)coder { 31 | [coder encodeDouble:_stayTime forKey:@"stayTime"]; 32 | [coder encodeInteger:_viewCount forKey:@"viewCount"]; 33 | } 34 | 35 | @end 36 | 37 | 38 | 39 | @implementation UIViewController (RRStatistics) 40 | 41 | -(void)setStatisticName:(NSString *)statisticName { 42 | IMP key = class_getMethodImplementation([self class],@selector(statisticName)); 43 | objc_setAssociatedObject(self, key, statisticName, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 44 | } 45 | 46 | -(NSString *)statisticName { 47 | IMP key = class_getMethodImplementation([self class],@selector(statisticName)); 48 | id obj = objc_getAssociatedObject(self,key); 49 | if(!obj) 50 | obj = NSStringFromClass(self.class); 51 | return obj; 52 | } 53 | 54 | -(BOOL)statisticEnabled { 55 | IMP key = class_getMethodImplementation([self class],@selector(statisticEnabled)); 56 | NSNumber *num = objc_getAssociatedObject(self,key); 57 | if(num) { 58 | return num.boolValue; 59 | } 60 | else { 61 | 62 | if([self isKindOfClass:[UIAlertController class]]) 63 | return NO; 64 | 65 | return self.childViewControllers.count == 0; 66 | } 67 | } 68 | 69 | -(void)setEnableStatistic:(BOOL)enableStatistic { 70 | IMP key = class_getMethodImplementation([self class],@selector(statisticEnabled)); 71 | objc_setAssociatedObject(self, key, [NSNumber numberWithBool:enableStatistic], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 72 | } 73 | 74 | -(BOOL)isInModalPresenting { 75 | 76 | //Tips:如果self是present包裹在navigationController中的vc,那么:\ 77 | 1> self.presentingViewController和self.navigationController.presentingViewController都是同一个 \ 78 | 2> self.modalPresentationStyle和self.navigationController.modalPresentationStyle不一定相等 79 | 80 | if(!self.presentingViewController) 81 | return NO; 82 | 83 | UIViewController *actualPresentedVc = self.presentingViewController.presentedViewController; 84 | 85 | if(actualPresentedVc.presentingViewController) { 86 | if(actualPresentedVc.modalPresentationStyle == UIModalPresentationFullScreen 87 | || actualPresentedVc.modalPresentationStyle == UIModalPresentationCurrentContext) 88 | return NO; 89 | else 90 | return YES; 91 | } 92 | return NO; 93 | } 94 | 95 | /** 96 | 注意:这里不能以-viewWillDisappear:或viewDidDisappear两个方法作为页面退出依据. 97 | 因为presentViewController如果不是UIModalPresentationFullScreen或UIModalPresentationCurrentContext模式的话,是不会触发presentingViewController的上述两个方法的 98 | 99 | modal模式,指viewController以"非"UIModalPresentationFullScreen或UIModalPresentationCurrentContext方式被present展现,这种情况下presentingViewController是不会执行-viewWillDisappear:和-viewDidDisappear两个方法的, 100 | 101 | 页面访问实现逻辑 102 | (用vc代替viewController实例,statStack是统计记录堆栈) 103 | 1.在-viewWillDisappear 和 -viewDidAppear执行相应代码 104 | 105 | viewDidAppear: 106 | 1.是否为modal模式 107 | a>是:(如果存在的话)将正在统计的vc入栈 108 | b>否:do nothing 109 | 2.停止正在统计vc的统计 110 | 3.当前是否需要统计 111 | a>是:开始统计vc 112 | b>否:do nothing 113 | 114 | 115 | viewWillDisappear: 116 | 1.vc是否正在统计中: 117 | a>是:停止统计 118 | b>否:do nothing 119 | 2.是否为modal模式 120 | a>是:恢复栈顶vc统计 121 | b>否:do nothing 122 | 123 | */ 124 | 125 | 126 | static NSMutableDictionary *sRRStatDic; 127 | static NSPointerArray *sRRStatStack; 128 | __weak UIViewController *sRRStatCurrentViewController; //当前正在统计的VC 129 | 130 | 131 | +(void)load { 132 | if(!sRRStatStack) 133 | sRRStatStack = [NSPointerArray weakObjectsPointerArray]; 134 | 135 | [self rrReadStatistics]; 136 | } 137 | 138 | +(void)rrEndStatisticViewController:(UIViewController *)viewController { 139 | 140 | // NSLog(@"----->> end:%@",NSStringFromClass(viewController.class)); 141 | 142 | if(!viewController) 143 | return; 144 | 145 | CFAbsoluteTime t0 = CFAbsoluteTimeGetCurrent(); 146 | 147 | RRViewControllerStatistic *stc = [sRRStatDic objectForKey:viewController.statisticName]; 148 | if(stc.enterTime > 0) { 149 | stc.stayTime += (t0 - stc.enterTime); 150 | } 151 | 152 | sRRStatCurrentViewController = nil; 153 | } 154 | 155 | +(void)rrBeginStatisticViewController:(nonnull UIViewController *)viewController fromRecover:(BOOL)revocer { 156 | 157 | CFAbsoluteTime t0 = CFAbsoluteTimeGetCurrent(); 158 | 159 | RRViewControllerStatistic *stc = [sRRStatDic objectForKey:viewController.statisticName]; 160 | if(!stc) { 161 | if(revocer) { 162 | return; //如果是恢复的话,之前没有记录就不再做任何事情,直接返回 163 | } 164 | else { 165 | stc = [RRViewControllerStatistic new]; 166 | [sRRStatDic setObject:stc forKey:viewController.statisticName]; 167 | } 168 | } 169 | 170 | stc.viewCount += 1; 171 | stc.enterTime = t0; 172 | 173 | sRRStatCurrentViewController = viewController; 174 | 175 | // NSLog(@"*****>> Begin:%@ recover:%d",NSStringFromClass(viewController.class),revocer); 176 | } 177 | 178 | //只在-viewDidAppear:方法中调用 179 | +(void)staticviewDidAppearForViewController:(UIViewController *)viewController { 180 | 181 | if([self rrShouldIgnoreSysViewController:viewController]) 182 | return; 183 | 184 | //viewController是否为modal模式,将正在统计的vc入栈 185 | if(viewController.isInModalPresenting) { 186 | if(sRRStatCurrentViewController) 187 | [sRRStatStack addPointer:(__bridge void * _Nullable)(sRRStatCurrentViewController)]; 188 | } 189 | 190 | #if DEBUG 191 | // NSLog(@">>>>> DidAppear[%@] sRRStatStack.count %zu",NSStringFromClass(viewController.class),sRRStatStack.count); 192 | #endif 193 | 194 | //停止正在统计vc的统计 195 | [self rrEndStatisticViewController:sRRStatCurrentViewController]; 196 | 197 | //viewController是否需要统计 198 | if(viewController.statisticEnabled) { 199 | [self rrBeginStatisticViewController:viewController fromRecover:NO]; 200 | } 201 | 202 | [self rrSaveStatistics]; 203 | } 204 | 205 | //只在viewWillDisappear:方法中调用 206 | +(void)staticviewWillDisappearForViewController:(UIViewController *)viewController { 207 | 208 | 209 | if([self rrShouldIgnoreSysViewController:viewController]) 210 | return; 211 | 212 | //vc是否正在统计中,是的话停止统计 213 | if(viewController == sRRStatCurrentViewController) { 214 | [self rrEndStatisticViewController:viewController]; 215 | } 216 | 217 | 218 | #if DEBUG 219 | // NSLog(@"+++++ WillDisappear[%@] sRRStatStack.count %zu",NSStringFromClass(viewController.class),sRRStatStack.count); 220 | #endif 221 | 222 | //Tips:NavigationCotroller是modal模式情况下,其willDisappear会先于其子vc调用 223 | //viewController是否为modal模式,是的话恢复栈顶 224 | if(viewController.isInModalPresenting && sRRStatStack.count > 0) { 225 | 226 | if(sRRStatCurrentViewController) { 227 | [self rrEndStatisticViewController:sRRStatCurrentViewController]; 228 | } 229 | 230 | NSInteger idx = sRRStatStack.count-1; 231 | UIViewController *rVc = [sRRStatStack pointerAtIndex:idx]; 232 | if(rVc) { 233 | [self rrBeginStatisticViewController:rVc fromRecover:YES]; 234 | [sRRStatStack removePointerAtIndex:idx]; 235 | } 236 | } 237 | 238 | [self rrSaveStatistics]; 239 | 240 | 241 | } 242 | 243 | +(void)RRStaticAppEnterbackground { 244 | 245 | //停止记录并入栈 246 | if(sRRStatCurrentViewController) { 247 | [sRRStatStack addPointer:(__bridge void * _Nullable)(sRRStatCurrentViewController)]; 248 | [self rrEndStatisticViewController:sRRStatCurrentViewController]; 249 | } 250 | } 251 | 252 | +(void)RRStaticAppEnterForeground { 253 | 254 | //恢复计入并出栈 255 | if(sRRStatStack.count > 0) { 256 | NSInteger idx = sRRStatStack.count-1; 257 | UIViewController *rVc = [sRRStatStack pointerAtIndex:idx]; 258 | if(rVc) { 259 | [self rrBeginStatisticViewController:rVc fromRecover:YES]; 260 | [sRRStatStack removePointerAtIndex:idx]; 261 | } 262 | } 263 | } 264 | 265 | +(BOOL)rrShouldIgnoreSysViewController:(UIViewController *)viewController { 266 | 267 | NSArray *classes = @[@"UIInputWindowController", 268 | @"UIEditingOverlayViewController", 269 | @"UIAlertController", 270 | @"UISystemKeyboardDockController", 271 | @"UINavigationController", 272 | @"UITabBarController", 273 | @"UICompatibilityInputViewController", 274 | @"UITableViewController", 275 | @"UISearchController", 276 | @"UIPredictionViewController"]; 277 | for(NSString *s in classes) { 278 | // Class cls = NSClassFromString(s); 279 | // if([s isKindOfClass:cls]) 280 | // return YES; 281 | 282 | NSString *clsStr = NSStringFromClass(viewController.class); 283 | if([s isEqualToString:clsStr]) 284 | return YES; 285 | } 286 | return NO; 287 | } 288 | 289 | 290 | +(NSURL *)rrStatisticsArchiveUrl { 291 | NSURL *libraryDir = [[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject]; 292 | return [libraryDir URLByAppendingPathComponent:@"RR.Statistics.arc"]; 293 | } 294 | 295 | +(void)rrReadStatistics { 296 | if(!sRRStatDic) { 297 | sRRStatDic = [NSKeyedUnarchiver unarchiveObjectWithFile:[self rrStatisticsArchiveUrl].path]; 298 | if(!sRRStatDic) 299 | sRRStatDic = [NSMutableDictionary dictionaryWithCapacity:128]; 300 | } 301 | } 302 | 303 | +(void)rrSaveStatistics { 304 | [NSKeyedArchiver archiveRootObject:sRRStatDic toFile:[self rrStatisticsArchiveUrl].path]; 305 | } 306 | 307 | +(NSDictionary *)rrStatisticsData { 308 | return sRRStatDic.copy; 309 | } 310 | 311 | +(NSString *)stringifyStatisticsWithTopStayedByCount:(unsigned int)topCount { 312 | NSArray *allKeys = [sRRStatDic allKeys]; 313 | NSArray *sortedNames = [allKeys sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { 314 | 315 | RRViewControllerStatistic *s1 = [sRRStatDic objectForKey:(NSString *)obj1]; 316 | RRViewControllerStatistic *s2 = [sRRStatDic objectForKey:(NSString *)obj2]; 317 | 318 | if(s1.stayTime > s2.stayTime) 319 | return NSOrderedAscending; 320 | else if(s1.stayTime < s2.stayTime) 321 | return NSOrderedDescending; 322 | else 323 | return NSOrderedSame; 324 | }]; 325 | 326 | NSMutableString *mString = [NSMutableString stringWithCapacity:5000]; 327 | int i = 0; 328 | for(NSString *n in sortedNames) { 329 | RRViewControllerStatistic *s1 = [sRRStatDic objectForKey:n]; 330 | [mString appendFormat:@"[%@: stayed %.3f(mins), viewed %zu];\n",n,s1.stayTime/60.0,s1.viewCount]; 331 | i++; 332 | if(i >= topCount) 333 | break; 334 | } 335 | 336 | return mString; 337 | } 338 | 339 | @end 340 | --------------------------------------------------------------------------------