RTRootNavigationController实现了什么?
1、可以像往常一样使用导航栏
2、可以在不同的控制器里实现不同风格的导航效果
3、内部已经实现了UIScreenEdgePanGestureRecognizer滑动返回手势,整屏滑动
4、对于导航栏的隐藏,屏幕旋转等等操作并不影响
具体使用
1、当UITabBarController作为window的rootControoler,则Tabbar的每一个子控制器的根控制器是RTRootNavigationController
//rootViewController
TabBarController *tabbar = [[TabBarController alloc]init];
RTRootNavigationController *firstContainVC = [[RTRootNavigationController alloc]initWithRootViewController:[[FirstViewController alloc]init]];
firstContainVC.tabBarItem = [[UITabBarItem alloc]initWithTitle:@"第一页" image:nil tag:0];
RTRootNavigationController *secondContainVC = [[RTRootNavigationController alloc]initWithRootViewController:[[SecondViewController alloc]init]];
secondContainVC.tabBarItem = [[UITabBarItem alloc]initWithTitle:@"第二页" image:nil tag:1];
tabbar.viewControllers = @[firstContainVC,secondContainVC];
self.window.rootViewController = tabbar;
2、由于实际项目只有个别页面导航不同,所以我们可以定义一个基类BaseViewController,实现总体导航风格,并设置返回按钮样式
@implementation BaseViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.navigationController.navigationBar.barTintColor = [UIColor redColor];
self.navigationController.navigationBar.tintColor = [UIColor redColor];
[self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor]}];
//默认使用的是RTRoot框架内部的导航效果和返回按钮,如果要自定义,必须将此属性设置为NO,然后实现下方方法;self.rt_navigationController.useSystemBackBarButtonItem = NO;
}
- (UIBarButtonItem *)rt_customBackItemWithTarget:(id)target action:(SEL)action{
}
以上设置导航的方法,可以在每个控制器里使用,设置自己的导航
3、自定义导航栏实现,实现此方法
- (Class)rt_navigationBarClass
{
return [RTCustomNavigationBar class];
}
4、push控制器的方法
要想实现每个控制器都有自己的导航栏,push操作的对象必须是self.rt_navigationController, 方法中有一个complete回调,在此回调方法中,我们还可以移除push前的控制器[self.rt_navigationController removeViewController:self];
[self.rt_navigationController pushViewController:detailVC animated:YES complete:nil];
5、隐藏导航栏,只需要一句话,千万记住可不是rt_navigationController
self.navigationController.navigationBar.hidden = YES
RTRootNavigationController的内部实现
使用RTRootNavigationController后的层级关系
--UITabBarController
---RTRootNavigationController
------RTContainerController
--------RTContainerNavigationController
-----------UIViewController
---RTRootNavigationController
------RTContainerController
--------RTContainerNavigationController
-----------UIViewController
为什么RTRootNavigationController的childViewControllers是RTContainerController
1、目的是为了让每个controller有自己的导航
2、如何做到的?
我们都知道UINavigationController初始化UIVIewController之后,都会执行pushViewController: animated:(BOOL)animated 方法,我们来分析一下RTRootNavigationController里面的这个方法的实现
- (void)pushViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
if (self.viewControllers.count > 0) {
UIViewController *currentLast = RTSafeUnwrapViewController(self.viewControllers.lastObject); //获取到当前controller
[super pushViewController:RTSafeWrapViewController(viewController,
viewController.rt_navigationBarClass,
self.useSystemBackBarButtonItem,
currentLast.navigationItem.backBarButtonItem,
currentLast.title) animated:animated];
}
else {
[super pushViewController:RTSafeWrapViewController(viewController, viewController.rt_navigationBarClass)
animated:animated];
}
}
1、核心方法 RTSafeWrapViewController,内部实现是这样的:
1.1 初始化一个RTContainerController对象
1.2 要push的controller作为其contentViewController, 用要push的controller的navigationBarClass初始化一个RTContainerNavigationController
1.3 要push的controller作为RTContainerNavigationController的childViewController, RTContainerNavigationController作为RTContainerController的childViewController
初始化好的RTContainerController就是这样的:
--RTContainerController
----RTContainerNavigationController
------pushViewController
返回的RTContainerController就是RTRootNavigationController的push对象,就形成了以上的层级关系,所以代码中push操作一定是使用self.rt_navigationController,而不是self.navigationController(RTContainerNavigationController),
[RTContainerController containerControllerWithController:controller
navigationBarClass:navigationBarClass
withPlaceholderController:withPlaceholder];
- (instancetype)initWithController:(UIViewController *)controller
navigationBarClass:(Class)navigationBarClass
withPlaceholderController:(BOOL)yesOrNo
backBarButtonItem:(UIBarButtonItem *)backItem
backTitle:(NSString *)backTitle
{
self = [super init];
if (self) {
self.contentViewController = controller;
self.containerNavigationController = [[RTContainerNavigationController alloc] initWithNavigationBarClass:navigationBarClass
toolbarClass:nil];
if (yesOrNo) {
UIViewController *vc = [UIViewController new];
vc.title = backTitle;
vc.navigationItem.backBarButtonItem = backItem;
self.containerNavigationController.viewControllers = @[vc, controller];
}
else
self.containerNavigationController.viewControllers = @[controller];
[self addChildViewController:self.containerNavigationController];
[self.containerNavigationController didMoveToParentViewController:self];
}
return self;
}
接着分析:
1、 上面1.2步用要push的controller的rt_navigationBarClass初始化一个RTContainerNavigationController,这里就实现了用自定义的UINavigationBar创建导航,controller内部实现rt_navigationBarClass的get方法就可以了
/* Use this initializer to make the navigation controller use your custom bar class.
Passing nil for navigationBarClass will get you UINavigationBar, nil for toolbarClass gets UIToolbar.
The arguments must otherwise be subclasses of the respective UIKit classes.
*/
[[RTContainerNavigationController alloc] initWithNavigationBarClass:navigationBarClass toolbarClass:nil];
以上,就实现了在不同的controller里有不同的导航
关于返回按钮,框架单独做了处理,特别方便
1、self.rt_navigationController.useSystemBackBarButtonItem = NO;
2、实现 rt_customBackItemWithTarget:(id)target action:(SEL)action方法,返回自定义leftBarbuttonItem,返回的响应action需要的话可以自定义,不需要的话,就用target,就会响应框架内部的返回方法
在willShowViewController方法中,会判断是否实现上述条件,实现了就会调用自定义的
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
BOOL isRootVC = viewController == navigationController.viewControllers.firstObject;
if (!isRootVC) {
viewController = RTSafeUnwrapViewController(viewController);
BOOL hasSetLeftItem = viewController.navigationItem.leftBarButtonItem != nil;
if (hasSetLeftItem && !viewController.rt_hasSetInteractivePop) {
viewController.rt_disableInteractivePop = YES;
}
else if (!viewController.rt_hasSetInteractivePop) {
viewController.rt_disableInteractivePop = NO;
}
if (!self.useSystemBackBarButtonItem && !hasSetLeftItem) {
if ([viewController respondsToSelector:@selector(rt_customBackItemWithTarget:action:)]) {
viewController.navigationItem.leftBarButtonItem = [viewController rt_customBackItemWithTarget:self
action:@selector(onBack:)];
}
else if ([viewController respondsToSelector:@selector(customBackItemWithTarget:action:)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
viewController.navigationItem.leftBarButtonItem = [viewController customBackItemWithTarget:self
action:@selector(onBack:)];
#pragma clang diagnostic pop
}
else {
viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Back", nil)
style:UIBarButtonItemStylePlain
target:self
action:@selector(onBack:)];
}
}
}
if ([self.rt_delegate respondsToSelector:@selector(navigationController:willShowViewController:animated:)]) {
[self.rt_delegate navigationController:navigationController
willShowViewController:viewController
animated:animated];
}
}
层级关系变了,还能像平时一样获取指定层级的控制器吗
当然不能了,self.navigationController(RTContainerNavigationController) 和 self是一一对应的内部关系,而外部关系self.rt_navigationController是RTRootNavigationController,而其childViewControllers是RTContainerController,所以都不能直接获取到目的控制器,而要通过遍历和判断,举两个例子
1、获取真正执行push操作的导航控制器
self.rt_navigationController
- (RTRootNavigationController *)rt_navigationController
{
UIViewController *vc = self;
while (vc && ![vc isKindOfClass:[RTRootNavigationController class]]) {
vc = vc.navigationController;
}
return (RTRootNavigationController *)vc;
}
2、获取栈顶控制器
self.rt_navigationController.rt_topViewController
//分析
self.rt_navigationController.topViewController是栈顶的RTContainerController,上述我们讲过,
RTContainerController的contentViewController就是我们要的UIViewController,从这里也表明,将UIViewController和RTContainerNavigationController作为其属性存在的意义
彩蛋
podfile安装RTRootNavigationController之后,会发现有一个分类UINavigationController (RTInteractivePush),可以实现屏幕右侧的UIScreenEdgePanGestureRecognizer
//实现,控制器内部实现
- (UIViewController*)rt_nextSiblingController
返回右滑要push的controller
总结
框架内部用到很多runtime运行时,用于属性绑定(分类中就用此实现原本不能添加的属性); 框架中有自定义类似swift中的高阶函数;注意到很多数组的遍历都用到enumerateObjectsUsingBlock,有很多好的方式和方法以及规则都值得一看