自定义VCStack
背景介绍
最近开发的几个工程使用的都是系统的VCStack,即UITabbarController
+ UINavigationController
的方式。这是一个经典的组合,在现实的开发场景中基本已经能够满足需求。但是,最近几期UI稿和UE稿的设计规则,有点超出了这个既有框架的能力
- 遮罩全屏的半浮层
- 出栈入栈复杂的动画
- 跨VC堆栈的
pop
和push
操作
在现有的UITabbarController
+ UINavigationController
结构下,这些功能已经被实现,但是过程较为复杂,不少逻辑现在看来任有优化的空间,基于这个背景,打算写一个自定义的VCStack,解决系统空间的局限性
系统VCStack存在的困境
在构思自定义VCStack之前,回顾了一下系统控件在日常开发中存在的瓶颈,这些瓶颈在日查那个的业务开发中经常困扰着我们,拖累开发人员的效率。总结了一下,有以下几点:
- UI***Bar层级过高导致的页面遮挡问题
- 出/入栈动画支持不够友好的问题
是的,我们可以通过NavigationControllerDelegate的方式,在代理中完成自定义动画的实现。但是这个代理的接入往往强依赖在某一个页面,抽象的层次不够,复用性也不高。不符合要求
- 任意时间点getTopVC带来的问题
堆栈的操作往往伴随着动画,动画中包含时间,如果我们在不合适的时间节点getTopVC可能导致之后的UI操作完全失效。比如,view正在消失的时候获取topVC并在vc.view中增加UI的处理
- 布局标准的问题。
由于TopLayout和BottomLayout的存在,导致我们的布局原点在一些操作中可能发生改变,这样的情况需要一定的开发经验才能捕捉到。一旦人为遗漏就可能造成布局上的错误
- 交叉影响。
这里举一个例子:修改Navigation的backItem会导致系统默认的优化手势失效,需要复写此功能才能生效
- 指定堆栈的跳转。
系统当前没有提供一个统一的调度入口来解决跨VC的跳转的问题,当前的实现还是基于遍历来找到VC实现跳转
- 模态视图继续跳转的问题
这是一种经常出现的场景,模态一个视图,在这个模态视图的基础上还存在堆栈的操作。当前的实现大多是在模态的基础上再包一层NavigationController,让其具备堆栈操作的能力
上面几个case使我们自定义VCStack解决的核心问题,本文也会按照这几个痛点展开讲解是如何一一解决这些问题的
自定义VCStack是什么
先交代一下这个VCStack到底是什么,系统NavigationController的效果我们都不陌生,如何在不继承系统NavigationController的基础上实现一套自己的VCStack管理机制呢(保持效果一致的原则)?从日常的使用中,我们了解到系统的NavigationController其实一个堆栈管理器,之中最重要的是VC的管理,可能是顶层封装的原因使得我们对整个管理体系了解不多。但是有几点是可以猜测到的
1、所有的VC都拥有自己的View
2、所有的View都是在根Window上展示的
3、你看到的动画只是管理器让交互不再生硬做出的表象
意识到这三点,接下来就好办了,VC是独立的,可以在任意节点创建和销毁,我们的VCStack只需要管理他们的显示逻辑和已有的生命周期。所以VCStack只要找到切合的时间点叠加和管理这些VC即可。首先有个统一的入口
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
这个节点中window需要一个rootViewController,这是VCStack接入的切口,一个VC创建并作为RootViewController被VCStack持有,VCStackInstance.rootViewController作为参数给到Window。这一步操作已经为VCStack打下了基石,因为之后所有VC.view的叠加都有了rootView.接下来的事情就变的简单了
1、push操作将vc.view叠加到currentVC
2、pop操作将vc.view从上一个vc.view移除
这期间需要兼顾的东西还有很多,比如
1、vc生命周期的一致
2、手势操作
3、动画接入
对整个想做的事情有了一定的了解了之后,下面是一些实现中的细节
逐个击破
视图层级 + 布局原点
自定义VCStack不会再有TopLayout和BottomLayout这种预置依赖,所有的View的布局都将从window的(0,0)点开始布局。navigationBar
和TabBar
也将会被CustomView代替以此抹平层级间Z轴差距过大导致的遮罩问题
[图片上传中...(系统navigation层级.png-6d0e8b-1545878789170-0)]
当Window的整个区域都有权限去管理之后,层级和布局原点的问题就已经不是问题了,但是这样又引入了其他问题:
- 自定义navigationBar增加了每个页面开发的成本
- 自定义TabBar增加了每个页面开发的成本
一个好的方法就是创建一个快捷的模板类,将常用的NavigationBar和常用的TabBar封装成模板输出,增加开发效率
@interface UIViewController (NavigationBar)
- (HDDefaultNaviBar *)defaultBar;
@end
- (HDDefaultNaviBar *)defaultBar {
HDDefaultNaviBar *customerBar = [[HDDefaultNaviBar alloc] initWithFrame:CGRectMake(0, 0, HDScreenInfo.width, HDScreenInfo.navigationBarHeight + HDScreenInfo.statusBarHeight)];
customerBar.backgroundColor = [UIColor whiteColor];
customerBar.title = @"测试title";
customerBar.backIcon = [UIImage imageNamed:@"NaviBack"];
customerBar.backAction = ^{
[self.vcStack popWithAnimation:[HDVCStackAnimation defaultAnimation]];
};
return customerBar;
}
动画拓展性
系统的Navigation堆栈的跳转提供的api并不多
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated; // Uses a horizontal slide transition. Has no effect if the view controller is already in the stack
- (nullable UIViewController *)popViewControllerAnimated:(BOOL)animated; // Returns the popped controller.
跳转中动画的支持方式为Bool值,这就限定了跳转中的动画拓展性。当然,设计系统的人为了能让跳转中的动画得到更高粒度的支持,实现了NavigationControllerDelegate这套协议,在集成了这套协议的VC中,可以将动画拓展的更好,协议如下:
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
但是任然有缺陷,细想一下,这样的协议是在哪个层面实现呢?
1、直接耦合到需要动画支持的VC?
2、抽象到UIViewController层面的统一代理?
1的方式在实际的使用中,算是较多的一种,但是存在拓展性和逻辑抽象的问题,相同的问题在另一个场景下,大多的复用方式是:copy + 粘贴。场景少还能理解,一旦这样场景多了,这种方式带来的问题就会凸显出来。渐渐的在使用系统VCStack的基调下,就会有人抽象这个层面的信息,做一个统一的管理,形成了2的这种方式,但是,2这种方式也是存在问题的,先看一下抽象层面的信息:
- currentVC
- willShowVC
- operation
关键点出在了operation,这是系统的枚举类型,和业务场景中的契合度不是很高,限制了动画的类型。这相当于找到了这个动画支持的痛点,现在讲一下我的思路:
在自定义的VCStack中将动画完全交出去,以实例的形式交出去,这看起来有点难以理解。如何统一实例的api?这就用到了协议。所有的animation实例是继承AnimationProtocol的,由这个协议来约束api,使得所有实例的调度一致。结构如下:
Animation设计.png
下面是实例的生成api,在实际的使用中每个独具特色的动画协议都是这么写的,他们的具体实现放在了集成的协议中
@interface HDVCStackAnimation : NSObject <HDVCStackAnimationProtocol>
+ (instancetype)defaultAnimation;
@end
协议本身和堆栈的逻辑保持一致
@protocol HDVCStackAnimationProtocol <NSObject>
- (void)pushWithWillShowVC:(UIViewController *)willShowVC
currentVC:(UIViewController *)currentVC
completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);
- (void)popWithWillShowVC:(UIViewController *)willShowVC
currentVC:(UIViewController *)currentVC
completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);
@end
协议的实现也是面向切面的,只需要关注当前的参数和逻辑,例如如下是一个模拟系统自带的堆栈动画的协议实现
@implementation HDVCStackAnimation
+ (instancetype)defaultAnimation {
return [HDVCStackAnimation new];
}
- (void)pushWithWillShowVC:(UIViewController *)willShowVC
currentVC:(UIViewController *)currentVC
completion:(void (^)(BOOL))completion {
// 动画开始前的UI效果
willShowVC.view.frame = CGRectMake(HDScreenInfo.width, 0, HDScreenInfo.width, HDScreenInfo.height);
[UIView animateWithDuration:0.34 animations:^{
willShowVC.view.frame = CGRectMake(HDScreenInfo.width / 3.0, 0, HDScreenInfo.width, HDScreenInfo.height);
currentVC.view.frame = CGRectMake(- HDScreenInfo.width / 3.0, 0, HDScreenInfo.width, HDScreenInfo.height);
} completion:^(BOOL finished) {
if (finished) {
/* 将对应View的frame还原
保持和无动画的逻辑对应
同时保证在UI调试时的正确性
*/
willShowVC.view.frame = CGRectMake(0, 0, HDScreenInfo.width, HDScreenInfo.height);
currentVC.view.frame = CGRectMake(0, 0, HDScreenInfo.width, HDScreenInfo.height);
}
completion(finished);
}];
}
- (void)popWithWillShowVC:(UIViewController *)willShowVC
currentVC:(UIViewController *)currentVC
completion:(void (^)(BOOL))completion {
// 动画开始前的UI效果
willShowVC.view.frame = CGRectMake(- HDScreenInfo.width / 3.0, 0, HDScreenInfo.width, HDScreenInfo.height);
currentVC.view.frame = CGRectMake(HDScreenInfo.width / 3.0, 0, HDScreenInfo.width, HDScreenInfo.height);
[UIView animateWithDuration:0.34 animations:^{
willShowVC.view.frame = CGRectMake(0, 0, HDScreenInfo.width, HDScreenInfo.height);
currentVC.view.frame = CGRectMake(HDScreenInfo.width, 0, HDScreenInfo.width, HDScreenInfo.height);
} completion:^(BOOL finished) {
completion(finished);
}];
}
@end
调用API的简化:
[self.vcStack pushto:vc animation:[HDVCStackAnimation defaultAnimation]];
可以看到,优化之后的动画api参数也是三个
- currentVC
- willShowVC
- AnimationInstance
但是这里的animationInstance实现的空间大大增加,他只要继承自AnimationProtocol,具体的animation如何实现已经完全交给了业务层。如果在业务层的设计上适配几套符合当前场景的animation,这样的抽象也会被简化到为数不多的Animation实例中。满足了我们的要求,拓展性和逻辑抽象
getTopVC + 交叉影响
在完全接手了VCStack之后,对于操作的每个细节都在开发者的掌握之中,当任务触达的时候,可以追加AnimationCompletionHandle的处理,来让这个逻辑更加健壮。同样的交叉影响的存在也被开发人员决定,只有设计中存在这种交叉影响,才会在使用中存在这样的逻辑。设计的节点已经被开发人员管控,需不需要这种逻辑交互已经不再是一个黑盒
getTopVC+交叉影响.png指定VC的跳转
这个功能在实际的业务中会经常遇到,在系统Navigation的基础上的实现如下
1、遍历navigationController.viewControllers
2、找到匹配的VC实例
3、执行popToVC操作
前面两步基本不可避免,导致在实际的落地式往往一堆一堆代码的存在,对于代码简洁来说不是一个很好的方案。考虑到这样的需求场景,VCStack中集成了一套快捷的跳转API,覆盖了常见的业务场景
/**
push 操作,向当前堆栈中r压入一个对象
@param vc 即将被入栈的viewController
@param animation 入栈动画
*/
- (void)pushto:(UIViewController *)vc
animation:(NSObject<HDVCStackAnimationProtocol> *)animation;
/**
出栈操作
@param animation 出栈动画
*/
- (void)popWithAnimation:(NSObject<HDVCStackAnimationProtocol> *)animation;
/**
出栈到根节点操作
@param animation 出栈动画类型
*/
- (void)popToRootViewControllerWithAnimation:(NSObject<HDVCStackAnimationProtocol> *)animation;
/**
出栈到指定的vc操作,匹配条件是当前的vc名称
@param vcName 即将要显示的vc名称
@param popAnimation 出栈动画
*/
- (void)popToVCWithName:(NSString *)vcName
animation:(NSObject<HDVCStackAnimationProtocol> *)popAnimation;
/**
出栈到指定的vc,匹配条件是实例对象的id指针是否相等
@param vc 即将要显示的vc实例
@param popAnimation 出栈动画
@param popCompletion 操作完成之后的回调,主要用于pop then push这种操作
*/
- (void)popTo:(UIViewController *)vc
animation:(NSObject<HDVCStackAnimationProtocol> *)popAnimation
popCompleteHandle:(void (^)(BOOL))popCompletion;
/**
出栈到指定的vc名称,之后再压栈到一个的vc
@param popVCName 即将在栈顶出现的vc名称
@param popAnimation 出栈动画
@param pushVC 即将压栈的vc实例
@param pushAnimation 压栈动画
*/
- (void)popToVCWithName:(NSString *)popVCName
animation:(NSObject<HDVCStackAnimationProtocol> *)popAnimation
thenPushTo:(UIViewController *)pushVC
animation:(NSObject<HDVCStackAnimationProtocol> *)pushAnimation;
/**
出栈到指定的vc实例,之后再压栈到一个的vc
@param popVC 即将在栈顶出现的vc名称
@param popAnimation 出栈动画
@param pushVC 即将压栈的vc实例
@param pushAnimation 压栈动画
*/
- (void)popTo:(UIViewController *)popVC
animation:(NSObject<HDVCStackAnimationProtocol> *)popAnimation
thenPushTo:(UIViewController *)pushVC
animation:(NSObject<HDVCStackAnimationProtocol> *)pushAnimation;
@end
逻辑的处理已经在VCStack内部完成,只需要简单的API调用就可以完成业务需求
模态视图后续堆栈跳转
如果在模态视图中还存在堆栈的跳转,系统VCStack基础下的处理基本是在modalVC上包装一层VCStack,使其具备这样的能力,但是这里会存在问题,两个navigationStack的间接断开,如果这里执行popToVC会带了大量的逻辑判断。使用了自定义VCStack可以将modal视图的出现规划到push操作中,只是这里的动画实例发生了改变
@implementation HDModelAnimation
+ (instancetype)defaultAnimation {
return [HDModelAnimation new];
}
- (void)pushWithWillShowVC:(UIViewController *)willShowVC
currentVC:(UIViewController *)currentVC
completion:(void (^)(BOOL))completion {
// 动画开始前的UI效果
willShowVC.view.frame = CGRectMake(0, HDScreenInfo.height, HDScreenInfo.width, HDScreenInfo.height);
[UIView animateWithDuration:0.34 animations:^{
willShowVC.view.frame = CGRectMake(0, 0, HDScreenInfo.width, HDScreenInfo.height);
} completion:^(BOOL finished) {
completion(finished);
}];
}
- (void)popWithWillShowVC:(UIViewController *)willShowVC
currentVC:(UIViewController *)currentVC
completion:(void (^)(BOOL))completion {
// 动画开始前的UI效果
[UIView animateWithDuration:0.34 animations:^{
currentVC.view.frame = CGRectMake(0, HDScreenInfo.height, HDScreenInfo.width, HDScreenInfo.height);
} completion:^(BOOL finished) {
completion(finished);
}];
}
@end
这样的操作和模态视图出现和消失的视觉效果等效,同时保持了VCStack链
[self.vcStack pushto:vc animation:[HDModelAnimation defaultAnimation]];
[self.vcStack popWithAnimation:[HDModelAnimation defaultAnimation]];
细节
在自定义VCStack中设计到很多细节操作,这些操作的完善会让整个VCStack更加的健壮
生命周期维护
在VCStack中除了view的依赖的管理,同步操作还需要将对应的VC的生命周期管理起来,在日常的业务场景中这几个生命周期使用的频次是最高的
- viewWillAppear
- viewDidAppear
- viewWillDisappear
- viewDidDisappear
- dealloc
为了保持和系统生命周期的一致性,在push和pop操作中对VC的生命周期做了手动处理
- (void)pushto:(UIViewController *)vc animation:(NSObject<HDVCStackAnimationProtocol> *)animation {
// 添加手势处理
[self panGestureWithView:vc];
// 当前禁止任何手势
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[self.viewControllers addObject:vc];
[vc viewWillAppear:false];
[self.visibleViewController viewWillDisappear:false];
[self.visibleViewController.view addSubview:vc.view];
vc.vcStack = self;
// 对底部的tabBar做层级操作
if (vc.hdHideBottomBarWhenPushed) {
// 这里什么都不做
[self.tabBarManager.view bringSubviewToFront:vc.view];
}
if (animation) {
// 动画开始
[animation pushWithWillShowVC:vc currentVC:self.visibleViewController completion:^(BOOL finished) {
if (finished) {
[self.visibleViewController viewDidDisappear:true];
[vc viewDidAppear:true];
self.visibleViewController = vc;
// 手势禁用关闭
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}
}];
}
else {
// 手势禁用关闭
[self.visibleViewController viewDidDisappear:false];
[vc viewDidAppear:false];
self.visibleViewController = vc;
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}
}
- (void)popToVC:(UIViewController *)popToVC
animation:(NSObject<HDVCStackAnimationProtocol> *)animation
willDismissVC:(UIViewController *)willDismissVC
popCompleteHandle:(void (^)(BOOL))popCompletion {
if (popToVC) {
// 基础引用链
willDismissVC.vcStack = nil;
// 当前禁止任何手势
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
if (animation) {
[popToVC viewWillAppear:true];
[willDismissVC viewWillDisappear:true];
[animation popWithWillShowVC:popToVC currentVC:willDismissVC
completion:^(BOOL finished) {
if (finished) {
[willDismissVC.view removeFromSuperview];
[willDismissVC viewDidDisappear:true];
[popToVC viewDidAppear:true];
self.visibleViewController = popToVC;
// 手势禁用关闭
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
// completion handle
if (popCompletion) {
popCompletion(finished);
}
}
}];
}
else {
[popToVC viewWillAppear:false];
[willDismissVC viewWillDisappear:false];
[willDismissVC.view removeFromSuperview];
[willDismissVC viewDidDisappear:false];
[popToVC viewDidAppear:false];
self.visibleViewController = popToVC;
// 手势禁用关闭
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
if (popCompletion) {
popCompletion(YES);
}
}
}
else {
if (popCompletion) {
popCompletion(NO);
}
}
}
对于dealloc 在持有链消失的时候能被系统检测到,可以正常的释放,当前的持有关系为:
- VCStack持有数组
- 数组持有VC
- vc弱持有VCStack
其中VC弱持有VCStack是为了兼容tabBarController的存在,如果工程是一个单一的VCStack完全可以用单例待提升实例。在pop的时候会主动解开所有的依赖
VC.vcStack = nil
VCStack.array remove VC
手势系统维护
在每次push的时候,都会在View的层级上增加手势系统,当然这里也有协议的支持,如果VC实现了协议
@protocol HDVCEnableDragBackProtocol <NSObject>
- (BOOL)enableDrag;
@end
并标记为NO的时候,这个页面是不支持手势的。具体实现如下:
- (void)pushto:(UIViewController *)vc animation:(NSObject<HDVCStackAnimationProtocol> *)animation {
// 添加手势处理
[self panGestureWithView:vc];
.......
}
- (void)pangestureWithView:(UIView *)view completeHandle:(void (^)(void))completeHandle {
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
self.successBlock = completeHandle;
[view addGestureRecognizer:panGesture];
}
- (void)pan:(UIPanGestureRecognizer *)pan {
// 当前正在拖动的view
UIView *view = pan.view;
// 即将要显示的View
if (self.viewControllers.count > 1) {
UIViewController *bottomViewController = self.viewControllers[self.viewControllers.count - 2];
UIView *bottomView = bottomViewController.view;
// 一些标记值
static CGPoint startViewCenter;
static CGPoint startBottomViewCenter;
static BOOL continueFlag = YES;
if (view && bottomView) {
// 拖动开始的检测
if (pan.state == UIGestureRecognizerStateBegan) {
// 拖动开始时View的frame需要先发生变化,保证和系统的UI风格统一
bottomView.frame = CGRectMake(- HDScreenInfo.width / 3.0, 0, HDScreenInfo.width, HDScreenInfo.height);
view.frame = CGRectMake(HDScreenInfo.width / 3.0, 0, HDScreenInfo.width, HDScreenInfo.height);
// 检测当前的拖动的位置是否在合适的点,当前确立,view的左边1/3z位置可以作为触发的初始点
CGPoint startPoint = [pan locationInView:view];
if (startPoint.x > (view.frame.size.width / 3.0)) {
continueFlag = NO;
}
else {
continueFlag = YES;
// 将底部的View遮罩,避免手势点击造成其他问题
[bottomView addSubview:self.maskView];
}
startViewCenter = view.center;
startBottomViewCenter = bottomView.center;
}
else if (pan.state == UIGestureRecognizerStateChanged) {
if (continueFlag) {
// 拿到对一个的偏移量
CGPoint transition = [pan translationInView:view];
view.center = CGPointMake(startViewCenter.x + transition.x / 3.0 * 2.0, startViewCenter.y);
bottomView.center = CGPointMake(startBottomViewCenter.x + transition.x / 3.0, startBottomViewCenter.y);
}
}
else if (pan.state == UIGestureRecognizerStateEnded) {
if (continueFlag) {
// 将遮罩view去除
if (self.maskView.superview != nil) {
[self.maskView removeFromSuperview];
}
// 开始收尾动画
if (view.center.x > (view.frame.size.width / 6.0 * 7.0)) {
if (self.successBlock) {
self.successBlock();
}
}
else {
// 禁止用户操作
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
// 还原到初始的位置
[UIView animateWithDuration:0.34 animations:^{
view.frame = CGRectMake(HDScreenInfo.width / 3.0, 0, HDScreenInfo.width, HDScreenInfo.height);
bottomView.frame = CGRectMake(- HDScreenInfo.width / 3.0, 0, HDScreenInfo.width, HDScreenInfo.height);
} completion:^(BOOL finished) {
if (finished) {
// 解开用户手势操作
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
// 还原对象的位置
view.frame = CGRectMake(0, 0, HDScreenInfo.width, HDScreenInfo.height);
bottomView.frame = CGRectMake(0, 0, HDScreenInfo.width, HDScreenInfo.height);
}
}];
}
}
}
}
}
}
动画期间手势隔离
自定义VCStack提供了很多便捷的操作API,这些api中很多是伴有animation 操作的,为了避免用户在animation期间响应手势导致一些未知的错误,在代码段做了容错
- (void)pushto:(UIViewController *)vc animation:(NSObject<HDVCStackAnimationProtocol> *)animation {
// 添加手势处理
[self panGestureWithView:vc];
// 当前禁止任何手势
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
........
if (animation) {
// 动画开始
[animation pushWithWillShowVC:vc currentVC:self.visibleViewController completion:^(BOOL finished) {
if (finished) {
.......
// 手势禁用关闭
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}
}];
}
else {
// 手势禁用关闭
.....
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}
}
// pop 也是同样的逻辑
// 在右滑手势中增加了底部的bottomVC的遮罩,避免左滑手势响应其他事件带来问题
if (pan.state == UIGestureRecognizerStateBegan) {
.....
else {
continueFlag = YES;
// 将底部的View遮罩,避免手势点击造成其他问题
[bottomView addSubview:self.maskView];
}
.......
}
......
else if (pan.state == UIGestureRecognizerStateEnded) {
if (continueFlag) {
// 将遮罩view去除
if (self.maskView.superview != nil) {
[self.maskView removeFromSuperview];
}
}
总结
在实现的过程中,一开始的实现是围绕着一个NavigationStack的方式去进行的,这在实际的开发中已经满足了大多需求,因为大多的app都是一个Navigation的方式管理的,即便底部存在多个业务窗口,但是在下一级页面都会关闭底部的这个入口。
为了支持系统tabBar和VCStack混合管理的方式,在原来的基础上集成了tabBarManager+VCStack。是的整体的逻辑更靠近系统TabBar+navigation的管理方式。
最后说一句项目还在完善中,如果有兴趣可以一并完善。项目地址如下
VCStack
VCStack+TabBarManager