系统默认导航栏的返回按钮和返回方式

在默认情况下,导航栏返回按钮长这个样子

导航栏默认返回按钮

导航栏左上角的返回按钮,其文本默认为上一个ViewController的标题,如果上一个ViewController没有标题,则为Back(中文环境下为“返回”)。

在默认情况下,导航栏返回的点击交互和滑动交互如下

默认导航栏交互

这些东西不需要任何设置和操作,因此也没有其他需要说明的地方。

自定义左上角的返回按钮

绝大多数情况下,我们都需要根据产品需求自定义左上角的返回按钮,虽然这对大多数开发者来说不是什么难事,但依然有几个问题值得注意。

替换左上角返回按钮

替换返回按钮非常简单,只需要在ViewController中创建一个UIBarButtonItem和一张图片,并为按钮添加相应的点击事件即可,代码如下

- (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]; }

我们来看一眼效果

替换返回按钮

调整按钮位置

我们可以看到,上面的按钮是有点偏右的,那如果我们想调整按钮的位置该怎么做呢?设置Frame显然是行不通的,因为导航栏的NavigationItem是个比较特殊的View,我们无法通过简单的调整Frame来的调整左右按钮的位置。但是在苹果提供的UIButtonBarItem 中有个叫做UIBarButtonSystemItemFixedSpace的控件,利用它,我们就可以轻松调整返回按钮的位置。具体使用方法如下

//创建返回按钮
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];

我们来看一眼效果

调整返回按钮位置

可以看到,我们的返回按钮已经紧靠着屏幕边缘。

这个方法同样适用于调整导航栏右侧的按钮

让滑动返回手势生效

如果使用自定义的按钮去替换系统默认返回按钮,会出现滑动返回手势失效的情况。解决方法也很简单,只需要重新添加导航栏的interactivePopGestureRecognizerdelegate即可。
首先为ViewContoller添加UIGestureRecognizerDelegate协议

然后设置代理

self.navigationController.interactivePopGestureRecognizer.delegate = self;

至此,我们已经将返回按钮替换为我们的自定义按钮,并使滑动返回重新生效。接下来,我们继续来解决交互上的问题。

全屏滑动返回

这个一个很常见的需求,网上解决方案也很多,这里将本人常用的方法贴到这里。仅供参考
实现全屏滑动返回仅需在导航栏给导航栏添加UIGestureRecognizerDelegate协议,并在ViewDidLoad中写入如下代码

// 获取系统自带滑动手势的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;

我们来看一眼效果(注意鼠标位置)

全屏滑动返回.gif

成功

这种方法的原理其实很简单,其实就是自定义一个全屏滑动手势,并将滑动事件设置为系统滑动事件,然后禁用系统滑动手势即可。handleNavigationTransition就是系统滑动的方法,虽然系统并未提供接口,但是我们我们可以通过runtime找到这个方法,因此直接调用即可。两位,不必担心什么私有API之类的问题,苹果如果按照方法名去判断是否使用私有API,那得误伤多少App。

NavigationBar切换动画的“终极解决方案”

本部分文字代码都较多,不想看这么多废话的同学请直接翻到末尾,文末附有下载地址,导入项目后,继承即可生效。

在改变了导航栏样式,实现了全屏滑动返回之后,我们有了一个看起来还不错的导航栏。但是我们滑动时的切换依然是系统自带的动画,如果遇到前一个界面的NavigationBar为透明或前后两个Bar颜色不一样,这种渐变式的动画看起来就会不太友好,尤其当前后两个界面其中一个界面的NavigationBar为透明或隐藏时,其效果更是惨不忍睹。

这个问题,其实很多App,比如天猫、美团等都通过一种“整体返回”的效果来解决这个问题。效果如下:

整体滑动返回

这种解决方案等于将两个NavigationBar独立开来,因此可以相对完美的解决导航栏滑动切换中的种种Bug。
接下来,我们来看看如何实现这种效果。

基本原理

以我个人的认知,实现这个效果有三种基本思路:

  1. 使用UINavigationController自带的setNavigationBarHidden: animated:方法来实现,每次push或pop时,在当前控制器的viewWillDisappear:中设置隐藏,在要跳转的控制器的viewWillAppear:中设置导航栏显示。
  2. 在每次Push前对当前页面进行截图并保存到数组,Pop时取数组最后一个元素显示,滑动结束后调用系统Pop方法并删除最后一张截图。
  3. 使用iOS 7之后开放的,UIViewControllerAnimatedTransitioning协议,来实现自定义导航栏转场动画及交互。

以上三种方法,方法一十分繁琐,而且会有很多莫名其妙的BUG,直接pass。

在iOS的交互中,push一般通过按钮的点击事件或View的tap事件触发,而pop则可能通过事件触发,也可能通过右滑手势触发。因此,我们将这个我们要实现的动画效果分为交互效果和无交互效果两种,下面我们将使用方法2和方法3提供的思路,分别实现这两种效果,这样就能较为完美的解决Push和Pop的动画问题。

实现交互动画效果

准备需要使用的数组及手势
#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]; }
实现手势的相应事件
// 响应手势的方法
- (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]; }]; }

}

实现截图保存功能,并在Push前截图
- (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]; }
重写常用的pop方法

在一开始基本原理地方,我们说过pop时要删除最后一张截图,用来保证数组中的最后一张截图是上一个控制器,但是很多情况下我们可能调用的是导航栏的popToViewController: animated:方法或popToRootViewControllerAnimated:来返回,这种情况下,我们删除的可能就不是一张截图,因此我们需要分别重写这些Pop方法,去确定我们要删除多少张图片,代码如下

- (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]; }
※在指定的控制器屏蔽手势

在上面代码中,我们使用的是侧滑手势,并将相应区域设置为屏幕左侧。
之所以不用全屏滑动,是因为全屏滑动手势在有些时候会和其他手势冲突,如果冲突的是我们自定义的手势,自然好解决,但如果是系统手势,如TableView的左滑菜单操作,这个事情就很蛋疼的。
但是如果必须要做全屏滑动手势的话,我们可以对代码稍作修改,某些控制器中屏蔽手势。

首先给导航栏添加禁用名单数组并配置

...
@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]; }

到了这里,我们已经完成了交互式的切换动画,效果跟开头一样,就不再截图。接下来我们来解决另一个大Boss-非交互式动画

实现非交互动画效果

理论基础

这里我们就要用到之前说的UIViewControllerAnimatedTransitioning来实现。限于篇幅,这里不再详细介绍这部分的基础知识,大家可以移步这两篇博客做一个初步的了解

向 UINavigationController 的传统动画说”再见” — 自定义过场动画(一)http://www.jianshu.com/p/f6d48c5814e1
iOS 7:自定义导航转场动画以及更多http://blog.sina.com.cn/s/blog_4ca9ceef0101ic5i.html

实现原理

注:FromVC代表即将消失的视图控制器,ToVC表示将要展示的视图控制器

我们要实现的效果:
Push的时候,FromVC往左移动,ToVC从屏幕右侧出现跟随FromVC左移直至FromVC消失,此时ToVC刚好完整显示在屏幕上。
Pop的时候,FromVC向右移动,ToVC从屏幕边缘出现跟随FromVC向右移动直至FromVC消失,此时ToVC刚好完整显示在屏幕上

实现的时候,我们依然需要将Push和Pop分开讨论
先说Pop
1.和交互式动画一样,每次Push时对屏幕截屏并保存,Pop的再次截屏但不保存
2.把Pop时截取的图片作为FromVC展示,把Push到这个界面时截取的图片作为ToVC展示
3.并对两张图片做位移动画,动画结束后移除两张图片

然后是Push
1.Push时先对当前屏幕截屏。
2.将截取的图片保存方便Pop回来时使用,并把这张图片作为这次Push的FromVC保存。
3.获取当前导航栏控制器对象,调整其Transform属性中的位移参数作为ToVC展示
4.对截图和导航栏做位移,动画结束后直接移除截屏图片

为什么要对导航栏作位移?

首先,在Push结束之前,我们是无法知道ToVC具体是什么样子,系统的截屏方法对于未加载出来的View是无能为力的,而UIView的 snapshotViewAfterScreenUpdates:方法又无法带着导航栏一起映射到一个新的View上,因此视觉效果很差。
正好在Pop的时候,为了达到想要的动画效果,用来展示的两张图片都需要放到导航栏的View上,因此在Push的时候我们就直接将导航栏的View做一个放射变换,当然,这也就意味着,当我们Push的时候,截屏就不能再放到导航栏上,而是应该放到它的“更上一层“ -- UITabbarController的View上

让我们撸一发代码

根据上述实现原理,我们可以知道,我们的主要工作重点在于打造一个合适的动画控制器。更准确的说,我们需要实现的细节都在UIViewControllerAnimatedTransitioning中,由于之前解释的很详细,这里我直接贴上相应代码供参考

-(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]; //若removeCount大于0 则说明Pop了不止一个控制器 if (_removeCount > 0) { for (NSInteger i = 0; i < _removeCount; i ++) { if (i == _removeCount - 1) { //当删除到要跳转页面的截图时,不再删除,并将该截图作为ToVC的截图展示 lastVcImgView.image = [self.screenShotArray lastObject]; _removeCount = 0; break; } else { [self.screenShotArray removeLastObject]; } } } else { lastVcImgView.image = [self.screenShotArray lastObject]; } 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; }

注:removeLastScreenShot需要在使用滑动手势Pop后调用,用来清除动画控制器中保存的截图,否则当交互式和非交互式动画交替使用时,会出现截图混乱的问题。

更新:

在调用 popToViewController:(UIViewController *)viewController animated:(BOOL)animated一次Pop多个页面,或调用popToRootViewControllerAnimated直接回到跟控制器时,一样需要清除对应数量的截图,并且需要和导航栏配合操作。新的代码已提交github,文章里也已经更新动画控制器对应的部分,具体代码还是以GitHub为准。

看看效果

我们将动画持续时间调制两秒,观察一下效果

完成效果.gif

后记

这篇文章开始于四个月之前,中间由于个人以及工作原因拖了又拖,终于在最近补完,逻辑混乱之处请见谅。

文/_奔跑的炸鸡(简书作者)
原文链接:http://www.jianshu.com/p/31f177158c9e
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
demo:https://github.com/RunningChicken-K/KLTAnimateNav

转载于:https://www.cnblogs.com/pioneerMax/p/5961531.html

一次性解决导航栏的所有问题相关推荐

  1. iOS 解决导航栏pop返回时出现黑块问题!

    iOS 解决导航栏pop返回时出现黑块问题! 问题描述: 导航栏正常从A页面push到B页面,从B页面pop返回A页面时遇到过渡过程中导航栏出现黑块的问题. 如截图所示: 问题原因: A界面导航栏被影 ...

  2. 解决导航栏按钮背景色切换,刷新页面,按钮背景色切换,页面和路径没有切换问题

    解决导航栏按钮背景色切换,刷新页面,按钮背景色切换,页面和路径没有切换问题 上篇博文中 点击导航栏切换背景色 有一个bug,如果点击第二个或者后面几个按钮,再刷新页面,按钮背景色会跳到第一个按钮上去, ...

  3. iOS11解决导航栏影藏返回文字的方法

    iOS11适配 : iOS适配工作 需求 如果要只保留返回按钮的文字,不需要"返回"文字 iOS11之前 在全局函数执行的地方使用一下代码: let barButton = UIB ...

  4. 解决Hexo博客导航栏链接URL乱码问题

    今年的计划之一是搭建一个博客,开始写博客.于是在网上找了一些博客程序发现用Hexo在gitHub上搭建自己的个人博客是比较简单而且易于维护的做法. 在网上找了一些教程后开始搭建,用自己比较中意的hex ...

  5. 基于CarSystemUI实现左侧导航栏NavigationBar及下拉面板定制开发1——Android10智能座舱

    文章目录 前言 一.需求说明 二.修改方案 1.基于需求的两种设计构想 2.修改正确的高度及宽度 三.CarSystemUI 1.CarOS框架关于CarSystemUI的介绍 2.替换CarSyst ...

  6. Android 标题栏及导航栏设计与实现

    文章目录 整体演示 一.Toolbar实现顶部标题 1.案例演示 2.实现步骤 2.1.隐藏页面自带标题栏 2.2.页面布局添加ToolBar 2.3.MainActivity.java(设置Tool ...

  7. 微信小程序自定义导航栏 胶囊菜单按钮高度完美适配 原理简单 赛过一些大厂的适配 妈妈再也不用担心我强迫症啦

    在自定义小程序导航栏时,右上角的胶囊(MenuButton)在不同机型测试,会发现很难适配. 实测中 不同的手机,胶囊高度不一样.状态栏高度不一样.与模拟器显示的情况是不一样的. 完全需要根据手机,动 ...

  8. ios 关于navigationController导航栏隐藏问题

    NavigationController导航栏设置隐藏,不知道你有没有尝试过下面这个方法. self.navigationController.navigationBarHidden = YES; 在 ...

  9. iOS导航栏使用总结

    点击上方"程序员大咖",选择"置顶公众号" 关键时刻,第一时间送达! 作者:风恣 文:https://www.jianshu.com/p/50cd38f2772 ...

最新文章

  1. 剑指offer:面试题05. 替换空格
  2. 大顶堆删除最大值_算法学习笔记(47): 二叉堆
  3. 5、【设计模式】单列模式
  4. [转]linux下TCP连接占用的资源
  5. 华为鸿蒙不再孤,华为鸿蒙OS系统不再孤单!又一款国产系统启动内测:再掀国产替代化...
  6. c语言经典面试题(附答案)
  7. 解决 Win11 打不开 Windows 安全中心 (Windows Defender)
  8. ln建立软链接出现错误:broken symbolic link to
  9. NOIP2017提高组模拟赛4 (总结)
  10. fsolve:对非线性方程组求解
  11. 数据分析师的任职要求——阿里巴巴
  12. 第四章分支结构程序设计
  13. apktool反编译问题
  14. 皮尔森相关性的相似度
  15. uniapp集成colorUI uView2.0 Vuex
  16. Pytorch中 maxpool的ceil_mode
  17. pyqt中treeview控件右键单击操作
  18. 2023 WordPress收费下载资源插件erphpdown v9.8.2 美化版
  19. w ndows10系统开机,wndows10系统设置开机自动启动adsafe软件的方法
  20. 汽车行业每周新闻纵览

热门文章

  1. 一亿人民币是什么概念?
  2. 无论商业模式怎么变,其最核心的东西,永远不变
  3. In fact, at this stage, neither the 65W
  4. golang利用反射写入excel的简单工具类
  5. bcp大容量复制实用工具_运行中的BCP(大容量复制程序)命令
  6. Excel 2007 Open XML文件结构~~~1
  7. hdu 2586 How far away ?(LCA模板)(倍增法)
  8. TCP/IP学习笔记(3)----IP,ARP,RARP协议
  9. dedesmc 手机端生成静态页
  10. [CodePlus2017]晨跑