iOS custom VCStack

Author of this article: Wang Handong

Background introduction

Several recently developed projects use the system's VCStack, that is, the UITabbarController+ UINavigationControllermethod. This is a classic combination that can basically meet the needs in real development scenarios. However, the design rules of the recent UI drafts and UE drafts are a bit beyond the capabilities of this existing framework

  • A semi-floating layer that masks the full screen
  • Pop and push complex animations
  • popSum pushoperations across VC stacks

Under the existing UITabbarController+ UINavigationControllerstructure, these functions have been implemented, but the process is more complicated, and many logics seem to have room for optimization. Based on this background, I plan to write a custom VCStack to solve the limitations of the system space

The predicament of the system VCStack

Before conceiving a custom VCStack, I reviewed the bottlenecks existing in the daily development of system controls. These bottlenecks often plagued us in the business development of the daily check and dragged down the efficiency of developers. To sum up, there are the following points:

  • The page occlusion problem caused by the high level of UI***Bar
  • The problem that the support for out/in stack animation is not friendly enough是的,我们可以通过NavigationControllerDelegate的方式,在代理中完成自定义动画的实现。但是这个代理的接入往往强依赖在某一个页面,抽象的层次不够,复用性也不高。不符合要求
  • Problems caused by getTopVC at any point in time堆栈的操作往往伴随着动画,动画中包含时间,如果我们在不合适的时间节点getTopVC可能导致之后的UI操作完全失效。比如,view正在消失的时候获取topVC并在vc.view中增加UI的处理
  • A matter of layout standards.由于TopLayout和BottomLayout的存在,导致我们的布局原点在一些操作中可能发生改变,这样的情况需要一定的开发经验才能捕捉到。一旦人为遗漏就可能造成布局上的错误
  • cross influence.这里举一个例子:修改Navigation的backItem会导致系统默认的优化手势失效,需要复写此功能才能生效
  • Specifies a jump to the stack.系统当前没有提供一个统一的调度入口来解决跨VC的跳转的问题,当前的实现还是基于遍历来找到VC实现跳转
  • The problem with the modal view continuing to jump这是一种经常出现的场景,模态一个视图,在这个模态视图的基础上还存在堆栈的操作。当前的实现大多是在模态的基础上再包一层NavigationController,让其具备堆栈操作的能力

The above cases allow us to customize the core problems solved by VCStack. This article will also explain how to solve these problems one by one according to these pain points.

What is a custom VCStack

First, let me explain what this VCStack is. We are all familiar with the effects of the system NavigationController. How can we implement our own VCStack management mechanism without inheriting the system NavigationController (the principle of maintaining the same effect)? From daily use, we learned that the NavigationController of the system is actually a stack manager, the most important of which is the management of VC, which may be the reason for the top-level encapsulation, so we don't know much about the entire management system. But a few things can be guessed

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)点开始布局。navigationBarTabBar也将会被CustomView代替以此抹平层级间Z轴差距过大导致的遮罩问题 [图片上传中...(系统navigation层级.png-6d0e8b-1545878789170-0)]

VCStack-01.png

当Window的整个区域都有权限去管理之后,层级和布局原点的问题就已经不是问题了,但是这样又引入了其他问题:

  1. 自定义navigationBar增加了每个页面开发的成本
  2. 自定义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,使得所有实例的调度一致。结构如下:

VCStack-02.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的处理,来让这个逻辑更加健壮。同样的交叉影响的存在也被开发人员决定,只有设计中存在这种交叉影响,才会在使用中存在这样的逻辑。设计的节点已经被开发人员管控,需不需要这种逻辑交互已经不再是一个黑盒

VCStack-03.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

Guess you like

Origin juejin.im/post/6976932258586296357