让我们一次性解决导航栏的所有问题

etun0094 3年前
   <h3><strong>前言</strong></h3>    <p>今天我们来重点讨论导航栏返回的问题,包括各种问题的解决方案。</p>    <h3><strong>系统默认导航栏的返回按钮和返回方式</strong></h3>    <p>在默认情况下,导航栏返回按钮长这个样子</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/d2393990db793bbc3dfd27e624ea7cb1.png"></p>    <p style="text-align: center;">导航栏默认返回按钮</p>    <p>导航栏右上角的返回按钮,其文本默认为上一个ViewController的标题,如果上一个ViewController没有标题,则为Back(中文环境下为“返回”)。</p>    <p>在默认情况下,导航栏返回的点击交互和滑动交互如下</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/43a0e939a6c873d30813471ee700731c.gif"></p>    <p style="text-align: center;">默认导航栏交互</p>    <p>这些东西不需要任何设置和操作,因此也没有其他需要说明的地方。</p>    <h3><strong>自定义左上角的返回按钮</strong></h3>    <p>绝大多数情况下,我们都需要根据产品需求自定义左上角的返回按钮,虽然这对大多数开发者来说不是什么难事,但依然有几个问题值得注意。</p>    <p><strong>替换左上角返回按钮</strong></p>    <p>替换返回按钮非常简单,只需要在ViewController中创建一个UIBarButtonItem和一张图片,并为按钮添加相应的点击事件即可,代码如下</p>    <pre>  - (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view.    UIButton * leftBtn = [UIButton buttonWithType:UIButtonTypeSystem];  leftBtn.frame = CGRectMake(0, 0, 25,25);  [leftBtn setBackgroundImage:[UIImage imageNamed:@"nav_back"] forState:UIControlStateNormal];  [leftBtn addTarget:self action:@selector(leftBarBtnClicked:) forControlEvents:UIControlEventTouchUpInside];  self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:leftBtn];  }  - (void)leftBarBtnClicked:(UIButton *)btn  {   [self.navigationController popViewControllerAnimated:YES];  }</pre>    <p>我们来看一眼效果</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c717aa5e0d9754c1097ff327c24fc577.png"></p>    <p style="text-align: center;">替换返回按钮</p>    <p><strong>调整按钮位置</strong></p>    <p>我们可以看到,上面的按钮是有点偏左的,那如果我们想调整按钮的位置该怎么做呢?设置Frame显然是行不通的,因为导航栏的NavigationItem是个比较特殊的View,我们无法通过简单的调整Frame来的调整左右按钮的位置。但是在苹果提供的 UIButtonBarItem 中有个叫做 UIBarButtonSystemItemFixedSpace 的控件,利用它,我们就可以轻松调整返回按钮的位置。具体使用方法如下</p>    <pre>  //创建返回按钮  UIButton * leftBtn = [UIButton buttonWithType:UIButtonTypeSystem];  leftBtn.frame = CGRectMake(0, 0, 25,25);  [leftBtn setBackgroundImage:[UIImage imageNamed:@"icon_back"] forState:UIControlStateNormal];  [leftBtn addTarget:self action:@selector(leftBarBtnClicked:) forControlEvents:UIControlEventTouchUpInside];  UIBarButtonItem * leftBarBtn = [[UIBarButtonItem alloc]initWithCustomView:leftBtn];;  //创建UIBarButtonSystemItemFixedSpace  UIBarButtonItem * spaceItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];  //将宽度设为负值  spaceItem.width = -15;  //将两个BarButtonItem都返回给NavigationItem  self.navigationItem.leftBarButtonItems = @[spaceItem,leftBarBtn];</pre>    <p>我们来看一眼效果</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/48f8c376ab3b3e5d97dc7a2eccd99684.png"></p>    <p style="text-align: center;">调整返回按钮位置</p>    <p>可以看到,我们的返回按钮已经紧靠着屏幕边缘。</p>    <p>这个方法同样适用于调整导航栏右侧的按钮</p>    <p><strong>让滑动返回手势生效</strong></p>    <p>如果使用自定义的按钮去替换系统默认返回按钮,会出现滑动返回手势失效的情况。解决方法也很简单,只需要重新添加导航栏的 interactivePopGestureRecognizer 的 delegate 即可。</p>    <p>首先为ViewContoller添加 UIGestureRecognizerDelegate 协议</p>    <p>然后设置代理</p>    <pre>  self.navigationController.interactivePopGestureRecognizer.delegate = self;</pre>    <p>至此,我们已经将返回按钮替换为我们的自定义按钮,并使滑动返回重新生效。接下来,我们继续来解决交互上的问题。</p>    <p><strong>全屏滑动返回</strong></p>    <p>这个一个很常见的需求,网上解决方案也很多,这里将本人常用的方法贴到这里。仅供参考</p>    <p>实现全屏滑动返回仅需在导航栏给导航栏添加 UIGestureRecognizerDelegate 协议,并在ViewDidLoad中写入如下代码</p>    <pre>  // 获取系统自带滑动手势的target对象  id target = self.interactivePopGestureRecognizer.delegate;    // 创建全屏滑动手势,调用系统自带滑动手势的target的action方法  UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)];    // 设置手势代理,拦截手势触发  pan.delegate = self;    // 给导航控制器的view添加全屏滑动手势  [self.view addGestureRecognizer:pan];    // 禁止使用系统自带的滑动手势  self.interactivePopGestureRecognizer.enabled = NO;</pre>    <p>我们来看一眼效果(注意鼠标位置)</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/e86948ebff56df6ec0aa40a2dd3d5c8e.gif"></p>    <p style="text-align: center;">全屏滑动返回.gif</p>    <p>成功</p>    <p>这种方法的原理其实很简单,其实就是自定义一个全屏滑动手势,并将滑动事件设置为系统滑动事件,然后禁用系统滑动手势即可。 handleNavigationTransition 就是系统滑动的方法,虽然系统并未提供接口,但是我们我们可以通过runtime找到这个方法,因此直接调用即可。两位,不必担心什么私有API之类的问题,苹果如果按照方法名去判断是否使用私有API,那得误伤多少App。</p>    <h3><strong>NavigationBar切换动画的“终极解决方案”</strong></h3>    <p>本部分文字代码都较多,不想看这么多废话的同学请直接翻到末尾,文末附有下载地址,导入项目后,继承即可生效。</p>    <p>在改变了导航栏样式,实现了全屏滑动返回之后,我们有了一个看起来还不错的导航栏。但是我们滑动时的切换依然是系统自带的动画,如果遇到前一个界面的NavigationBar为透明或前后两个Bar颜色一样,这种渐变式的动画看起来就会不太友好,而当前后两个界面其中一个界面的 tabbar 为透明或隐藏时,其效果更是惨不忍睹。</p>    <p>这个问题,其实很多App,比如天猫、美团等都通过一种“整体返回”的效果来解决这个问题。效果如下:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/5d263668388fbe6d2c78b8439cef61e3.gif"></p>    <p style="text-align: center;">整体滑动返回</p>    <p>这种解决方案等于将两个NavigationBar独立开来,因此可以相对完美的解决导航栏滑动切换中的种种Bug。</p>    <p>接下来,我们来看看如何实现这种效果。</p>    <p>基本原理</p>    <p>以我个人的认知,实现这个效果有三种基本思路:</p>    <ol>     <li>使用 UINavigationController 自带的 setNavigationBarHidden: animated: 方法来实现,每次push或pop时,在当前控制器的 viewWillDisappear: 中设置隐藏,在要跳转的控制器的 viewWillAppear: 中设置导航栏显示。</li>     <li>在每次Push前对当前页面进行截图并保存到数组,Pop时取数组最后一个元素显示,滑动结束后调用系统Pop方法并删除最后一张截图。</li>     <li>使用iOS 7之后开放的,UIViewControllerAnimatedTransitioning协议,来实现自定义导航栏转场动画及交互。</li>    </ol>    <p>以上三种方法,方法一十分繁琐,而且会有很多莫名其妙的BUG,直接pass。</p>    <p>在iOS的交互中,push一般通过按钮的点击事件或View的 tap 事件触发,而pop则可能通过事件触发,也可能通过右滑手势触发。因此,我们将这个我们要实现的动画效果分为交互效果和无交互效果两种,分别实现这两种效果之后就能较为完美的解决Push和Pop的动画问题。</p>    <h3><strong>实现交互动画效果</strong></h3>    <p><strong>准备需要使用的数组及手势</strong></p>    <pre>  #define ScreenWidth [UIScreen mainScreen].bounds.size.width  #define ScreenHeight [UIScreen mainScreen].bounds.size.height  @interface LTNavigationController ()<UIGestureRecognizerDelegate>  @property(strong,nonatomic)UIImageView * screenshotImgView;  @property(strong,nonatomic)UIView * coverView;  @property(strong,nonatomic)NSMutableArray * screenshotImgs;  @property(strong,nonatomic)UIPanGestureRecognizer *panGestureRec;  @end    @implementation LTNavigationController    - (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view.    // 1,创建Pan手势识别器,并绑定监听方法  _panGestureRec = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureRec:)];  _panGestureRec.edges = UIRectEdgeLeft;  // 为导航控制器的view添加Pan手势识别器  [self.view addGestureRecognizer:_panGestureRec];    // 2.创建截图的ImageView  _screenshotImgView = [[UIImageView alloc] init];  // app的frame是包括了状态栏高度的frame  _screenshotImgView.frame = CGRectMake(0, 0, ScreenWidth, ScreenHeight);      // 3.创建截图上面的黑色半透明遮罩  _coverView = [[UIView alloc] init];  // 遮罩的frame就是截图的frame  _coverView.frame = _screenshotImgView.frame;  // 遮罩为黑色  _coverView.backgroundColor = [UIColor blackColor];    // 4.存放所有的截图数组初始化  _screenshotImgs = [NSMutableArray array];  }</pre>    <p><strong>实现手势的相应事件</strong></p>    <pre>  // 响应手势的方法  - (void)panGestureRec:(UIPanGestureRecognizer *)panGestureRec  {    // 如果当前显示的控制器已经是根控制器了,不需要做任何切换动画,直接返回  if(self.visibleViewController == self.viewControllers[0]) return;  // 判断pan手势的各个阶段  switch (panGestureRec.state) {      case UIGestureRecognizerStateBegan:          // 开始拖拽阶段          [self dragBegin];          break;        case UIGestureRecognizerStateEnded:          // 结束拖拽阶段          [self dragEnd];          break;        default:          // 正在拖拽阶段          [self dragging:panGestureRec];          break;  }  }    #pragma mark 开始拖动,添加图片和遮罩  - (void)dragBegin  {  // 重点,每次开始Pan手势时,都要添加截图imageview 和 遮盖cover到window中  [self.view.window insertSubview:_screenshotImgView atIndex:0];  [self.view.window insertSubview:_coverView aboveSubview:_screenshotImgView];    // 并且,让imgView显示截图数组中的最后(最新)一张截图  _screenshotImgView.image = [_screenshotImgs lastObject];  //_screenshotImgView.transform = CGAffineTransformMakeTranslation(ScreenWidth, 0);  }    // 默认的将要变透明的遮罩的初始透明度(全黑)  #define kDefaultAlpha 0.6    // 当拖动的距离,占了屏幕的总宽高的3/4时, 就让imageview完全显示,遮盖完全消失  #define kTargetTranslateScale 0.75  #pragma mark 正在拖动,动画效果的精髓,进行位移和透明度变化  - (void)dragging:(UIPanGestureRecognizer *)pan  {    // 得到手指拖动的位移  CGFloat offsetX = [pan translationInView:self.view].x;    // 让整个view都平移     // 挪动整个导航view  if (offsetX > 0) {      self.view.transform = CGAffineTransformMakeTranslation(offsetX, 0);    }      // 计算目前手指拖动位移占屏幕总的宽高的比例,当这个比例达到3/4时, 就让imageview完全显示,遮盖完全消失  double currentTranslateScaleX = offsetX/self.view.frame.size.width;    if (offsetX < ScreenWidth) {        _screenshotImgView.transform = CGAffineTransformMakeTranslation((offsetX - ScreenWidth) * 0.6, 0);  }    // 让遮盖透明度改变,直到减为0,让遮罩完全透明,默认的比例-(当前平衡比例/目标平衡比例)*默认的比例  double alpha = kDefaultAlpha - (currentTranslateScaleX/kTargetTranslateScale) * kDefaultAlpha;  _coverView.alpha = alpha;  }    #pragma mark 结束拖动,判断结束时拖动的距离作相应的处理,并将图片和遮罩从父控件上移除  - (void)dragEnd  {  // 取出挪动的距离  CGFloat translateX = self.view.transform.tx;  // 取出宽度  CGFloat width = self.view.frame.size.width;    if (translateX <= 40) {      // 如果手指移动的距离还不到屏幕的一半,往左边挪 (弹回)      [UIView animateWithDuration:0.3 animations:^{          // 重要~~让被右移的view弹回归位,只要清空transform即可办到          self.view.transform = CGAffineTransformIdentity;          // 让imageView大小恢复默认的translation          _screenshotImgView.transform = CGAffineTransformMakeTranslation(-ScreenWidth, 0);          // 让遮盖的透明度恢复默认的alpha 1.0          _coverView.alpha = kDefaultAlpha;      } completion:^(BOOL finished) {          // 重要,动画完成之后,每次都要记得 移除两个view,下次开始拖动时,再添加进来          [_screenshotImgView removeFromSuperview];          [_coverView removeFromSuperview];      }];  } else {      // 如果手指移动的距离还超过了屏幕的一半,往右边挪      [UIView animateWithDuration:0.3 animations:^{          // 让被右移的view完全挪到屏幕的最右边,结束之后,还要记得清空view的transform          self.view.transform = CGAffineTransformMakeTranslation(width, 0);          // 让imageView位移还原          _screenshotImgView.transform = CGAffineTransformMakeTranslation(0, 0);          // 让遮盖alpha变为0,变得完全透明          _coverView.alpha = 0;      } completion:^(BOOL finished) {          // 重要~~让被右移的view完全挪到屏幕的最右边,结束之后,还要记得清空view的transform,不然下次再次开始drag时会出问题,因为view的transform没有归零          self.view.transform = CGAffineTransformIdentity;          // 移除两个view,下次开始拖动时,再加回来          [_screenshotImgView removeFromSuperview];          [_coverView removeFromSuperview];            // 执行正常的Pop操作:移除栈顶控制器,让真正的前一个控制器成为导航控制器的栈顶控制器          [self popViewControllerAnimated:NO];      }];  }</pre>    <p>}</p>    <p><strong>实现截图保存功能,并在Push前截图</strong></p>    <pre>  - (void)screenShot  {  // 将要被截图的view,即窗口的根控制器的view  UIViewController *beyondVC = self.view.window.rootViewController;  // 背景图片 总的大小  CGSize size = beyondVC.view.frame.size;  // 开启上下文,使用参数之后,截出来的是原图(YES  0.0 质量高)  UIGraphicsBeginImageContextWithOptions(size, YES, 0.0);  // 要裁剪的矩形范围  CGRect rect = CGRectMake(0, 0, ScreenWidth, ScreenHeight);  //注:iOS7以后renderInContext:由drawViewHierarchyInRect:afterScreenUpdates:替代  [beyondVC.view drawViewHierarchyInRect:rect  afterScreenUpdates:NO];  // 从上下文中,取出UIImage  UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();  // 添加截取好的图片到图片数组  if (snapshot) {      [_screenshotImgs addObject:snapshot];  }  // 千万记得,结束上下文(移除栈顶的基于当前位图的图形上下文)  UIGraphicsEndImageContext();  }  - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated  {    //有在导航控制器里面有子控制器的时候才需要截图  if (self.viewControllers.count >= 1) {      // 调用自定义方法,使用上下文截图      [self screenShot];  }  // 截图完毕之后,才调用父类的push方法  [super pushViewController:viewController animated:YES];  }</pre>    <p><strong>重写常用的pop方法</strong></p>    <p>在一开始基本原理地方,我们说过pop时要删除最后一张截图,用来保证数组中的最后一张截图是上一个控制器,但是很多情况下我们可能调用的是导航栏的 popToViewController: animated: 方法或 popToRootViewControllerAnimated: 来返回,这种情况下,我们删除的可能就不是一张截图,因此我们需要分别重写这些Pop方法,去确定我们要删除多少张图片,代码如下</p>    <pre>  - (UIViewController *)popViewControllerAnimated:(BOOL)animated  {     [_screenshotImgs removeLastObject];     return [super popViewControllerAnimated:animated];  }  - (NSArray<UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated  {  for (NSInteger i = self.viewControllers.count - 1; i > 0; i--) {      if (viewController == self.viewControllers[i]) {          break;      }      [_screenshotImgs removeLastObject];  }    return [super popToViewController:viewController animated:animated];  }  - (NSArray<UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated  {  [_screenshotImgs removeAllObjects];  return [super popToRootViewControllerAnimated:animated];  }</pre>    <p><strong>※在指定的控制器屏蔽手势</strong></p>    <p>在上面代码中,我们使用的是侧滑手势,并将相应区域设置为屏幕左侧。</p>    <p>之所以不用全屏滑动,是因为全屏滑动手势在有些时候会和其他手势冲突,如果冲突的是我们自定义的手势,自然好解决,但如果是系统手势,如TableView的左滑菜单操作,这个事情就很蛋疼的。</p>    <p>但是如果必须要做全屏滑动手势的话,我们可以对代码稍作修改,某些控制器中屏蔽手势。</p>    <p>首先给导航栏添加禁用名单数组并配置</p>    <pre>  ...  @property(nonatomic,copy)NSArray * forbiddenArray;  ...  - (void)viewDidLoad {  [super viewDidLoad];  //原来代码  ...    //将手势禁用,之后在Push时根据条件开启   self.panGestureRec.enabled = enable  //将需要禁用手势的控制器的类名加到这个数组  self.forbiddenArray = @[@"SCViewController",@"ManageAddressViewController"];  }    - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated  {    //在指定控制器中禁用手势  解决滑动返回手势和某些手势冲突问题  BOOL enable = YES;  for (NSString * string in self.forbiddenArray) {      NSString * className = NSStringFromClass([viewController class]);      if ([string isEqualToString:className]) {          enable = NO;      }  }  self.panGestureRec.enabled = enable;    //原有代码  ...  }  - (UIViewController *)popViewControllerAnimated:(BOOL)animated  {  NSInteger count = self.viewControllers.count;  NSString * className = nil;  if (count >= 2) {      className = NSStringFromClass([self.viewControllers[count -2] class]);  }    BOOL enable = YES;  for (NSString * string in self.forbiddenArray) {      if ([string isEqualToString:className]) {          enable = NO;      }  }  self.panGestureRec.enabled = enable;  //原有代码  ...    return [super popViewControllerAnimated:animated];  }</pre>    <p>到了这里,我们已经完成了交互式的切换动画,效果跟开头一样,就不再截图。接下来我们来解决另一个大Boss-非交互式动画</p>    <h3><strong>实现交互动画效果</strong></h3>    <p><strong>理论基础</strong></p>    <p>这里我们就要用到之前说的 UIViewControllerAnimatedTransitioning 来实现。</p>    <p><strong>实现原理</strong></p>    <p>注:FromVC代表即将消失的视图控制器,ToVC表示将要展示的视图控制器</p>    <p>我们要实现的效果:</p>    <p>Push的时候,FromVC往左移动,ToVC从屏幕右侧出现跟随FromVC左移直至FromVC消失,此时ToVC刚好完整显示在屏幕上。</p>    <p>Pop的时候,FromVC向右移动,ToVC从屏幕边缘出现跟随FromVC向右移动直至FromVC消失,此时ToVC刚好完整显示在屏幕上</p>    <p>实现的时候,我们依然需要将Push和Pop分开讨论</p>    <p>先说Pop</p>    <p>1.和交互式动画一样,每次Push时对屏幕截屏并保存,Pop的再次截屏但不保存</p>    <p>2.把Pop时截取的图片作为FromVC展示,把Push到这个界面时截取的图片作为ToVC展示</p>    <p>3.并对两张图片做位移动画,动画结束后移除两张图片</p>    <p>然后是Push</p>    <p>1.Push时先对当前屏幕截屏。</p>    <p>2.将截取的图片保存方便Pop回来时使用,并把这张图片作为这次Push的FromVC保存。</p>    <p>3.获取当前导航栏控制器对象,调整其Transform属性中的位移参数作为ToVC展示</p>    <p>4.对截图和导航栏做位移,动画结束后直接移除截屏图片</p>    <p><strong>为什么要对导航栏作位移?</strong></p>    <p>首先,在Push结束之前,我们是无法知道ToVC具体是什么样子,系统的截屏方法对于未加载出来的View是无能为力的,而UIView的 snapshotViewAfterScreenUpdates: 方法又无法带着导航栏一起映射到一个新的View上,因此视觉效果很差。</p>    <p>正好在Pop的时候,为了达到想要的动画效果,用来展示的两张图片都需要放到导航栏的View上,因此在Push的时候我们就直接将导航栏的View做一个放射变换,当然,这也就意味着,当我们Push的时候,截屏就不能再放到导航栏上,而是应该放到它的“更上一层“ -- UITabbarController 的View上</p>    <p><strong>让我们撸一发代码</strong></p>    <p>根据上述实现原理,我们可以知道,我们的主要工作重点在于打造一个合适的动画控制器。更准确的说,我们需要实现的细节都在 UIViewControllerAnimatedTransitioning 中,由于之前解释的很详细,这里我直接贴上相应代码供参考</p>    <pre>  -(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext  {      UIImageView * screentImgView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenHeight)];  UIImage * screenImg = [self screenShot];  screentImgView.image =screenImg;    //取出fromViewController,fromView和toViewController,toView  UIViewController * fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];  //    UIView * fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];  UIViewController * toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];  UIView * toView = [transitionContext viewForKey:UITransitionContextToViewKey];      CGRect fromViewEndFrame = [transitionContext finalFrameForViewController:fromViewController];  fromViewEndFrame.origin.x = ScreenWidth;  CGRect fromViewStartFrame = fromViewEndFrame;  CGRect toViewEndFrame = [transitionContext finalFrameForViewController:toViewController];  CGRect toViewStartFrame = toViewEndFrame;        UIView * containerView = [transitionContext containerView];    if (self.navigationOperation == UINavigationControllerOperationPush) {          [self.screenShotArray addObject:screenImg];      //toViewStartFrame.origin.x += ScreenWidth;      [containerView addSubview:toView];        toView.frame = toViewStartFrame;        UIView * nextVC = [[UIView alloc]initWithFrame:CGRectMake(ScreenWidth, 0, ScreenWidth, ScreenHeight)];       //[nextVC addSubview:[toView snapshotViewAfterScreenUpdates:YES]];        [self.navigationController.tabBarController.view insertSubview:screentImgView atIndex:0];        //[self.navigationController.tabBarController.view addSubview:nextVC];      nextVC.layer.shadowColor = [UIColor blackColor].CGColor;      nextVC.layer.shadowOffset = CGSizeMake(-0.8, 0);      nextVC.layer.shadowOpacity = 0.6;        self.navigationController.view.transform = CGAffineTransformMakeTranslation(ScreenWidth, 0);        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{          //toView.frame = toViewEndFrame;      self.navigationController.view.transform = CGAffineTransformMakeTranslation(0, 0);          screentImgView.center = CGPointMake(-ScreenWidth/2, ScreenHeight / 2);          //nextVC.center = CGPointMake(ScreenWidth/2, ScreenHeight / 2);          } completion:^(BOOL finished) {            [nextVC removeFromSuperview];          [screentImgView removeFromSuperview];          [transitionContext completeTransition:YES];      }];    }  if (self.navigationOperation == UINavigationControllerOperationPop) {            fromViewStartFrame.origin.x = 0;      [containerView addSubview:toView];        UIImageView * lastVcImgView = [[UIImageView alloc]initWithFrame:CGRectMake(-ScreenWidth, 0, ScreenWidth, ScreenHeight)];      lastVcImgView.image = [self.screenShotArray lastObject];      screentImgView.layer.shadowColor = [UIColor blackColor].CGColor;      screentImgView.layer.shadowOffset = CGSizeMake(-0.8, 0);      screentImgView.layer.shadowOpacity = 0.6;      [self.navigationController.tabBarController.view addSubview:lastVcImgView];      [self.navigationController.tabBarController.view addSubview:screentImgView];       // fromView.frame = fromViewStartFrame;      [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{            screentImgView.center = CGPointMake(ScreenWidth * 3 / 2 , ScreenHeight / 2);          lastVcImgView.center = CGPointMake(ScreenWidth/2, ScreenHeight/2);          //fromView.frame = fromViewEndFrame;        } completion:^(BOOL finished) {          //[self.navigationController setNavigationBarHidden:NO];          [lastVcImgView removeFromSuperview];          [screentImgView removeFromSuperview];          [self.screenShotArray removeLastObject];          [transitionContext completeTransition:YES];        }];      }    }  - (void)removeLastScreenShot  {  [self.screenShotArray removeLastObject];  }  - (UIImage *)screenShot  {  // 将要被截图的view,即窗口的根控制器的view(必须不含状态栏,默认ios7中控制器是包含了状态栏的)  UIViewController *beyondVC = self.navigationController.view.window.rootViewController;  // 背景图片 总的大小  CGSize size = beyondVC.view.frame.size;  // 开启上下文,使用参数之后,截出来的是原图(YES  0.0 质量高)  UIGraphicsBeginImageContextWithOptions(size, YES, 0.0);  // 要裁剪的矩形范围  CGRect rect = CGRectMake(0, 0, ScreenWidth, ScreenHeight);  //注:iOS7以后renderInContext:由drawViewHierarchyInRect:afterScreenUpdates:替代  [beyondVC.view drawViewHierarchyInRect:rect  afterScreenUpdates:NO];  // 从上下文中,取出UIImage  UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();    // 千万记得,结束上下文(移除栈顶的基于当前位图的图形上下文)  UIGraphicsEndImageContext();        // 返回截取好的图片  return snapshot;    }</pre>    <p>注: removeLastScreenShot 需要在使用滑动手势Pop后调用,用来清除动画控制器中保存的截图,否则当交互式和非交互式动画交替使用时,会出现截图混乱的问题。</p>    <p><strong>看看效果</strong></p>    <p>我们将动画持续时间调制两秒,观察一下效果</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/4003f758c7925da7017baef83804e48b.gif"></p>    <p style="text-align: center;">完成效果.gif</p>    <p>使用方法:</p>    <p>1.将这四个文件导入工程</p>    <p>2.将需要动画的导航栏继承KLTNavigationController即可</p>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/31f177158c9e</p>    <p> </p>