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

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

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

导航栏默认返回按钮

导航栏左上角的返回按钮,其文本默认为上一个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

时间: 2024-11-05 06:01:49

一次性解决导航栏的所有问题的相关文章

【Bootstrap】导航栏navbar在PC上的缺陷与解决方案

在Bootstrap的官网上,提供了一种导航栏的组件: 只要在站点文件夹放好JQ与Bootstrap输入如下代码, <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml

IOS开发之Bug--iOS7View被导航栏遮挡问题的解决

在实际开发中,遇到在UITextView的frame等于当前控制器的View的frame的情况下,然后运行的时候,发现控制器的Frame的高度y值会从导航条的位置64变化到0. 导致UITextView的frame也跟着一起移动. 这个问题本质其实就是iOS7View被导航栏遮挡问题,于是经过百度搜索到答案.特此下面 复制拷贝 做个简单的笔记: self.navigationController.navigationBar.translucent = NO; 如果在iPad上用了split vi

导航栏布局时遇到的问题以及解决办法 css选择器优先级

得到的导航栏效果 添加#menu ul li{width:30px;} 效果如图 将会使列表项和分隔区域的宽度同时改变因为id选择器的优先级高于类选择器,此时应该为列表项添加内联样式如图 才能得到如下效果 或者可以使用第二种方法 #menu ul li{ float:left; margin-left:10px;text-align:center;padding-left:10px;实现列表项文字的居中 line-height:28px; height:28px; width:40px; bor

iOS解决隐藏导航栏后,打开照片选择器后导航栏不显示的问题以及更换导航栏背景色

问题描述: 遇到一种情况,在一个控制器上(隐藏了导航栏),打开照片选择器 UIImagePickerController后,照片选择器头部一片空白,且上滑相册时,信息会有错乱效果. 原因分析: 通过查看层次图,发现导航栏其实有的,那么问题是因为导航栏透明了导致的 解决办法: UIImagePickerController *picker = [[UIImagePickerController alloc] init]; picker.delegate = self; picker.sourceT

聊天界面使用IQKeyboardManager导航栏及整个页面上移的解决方法

问题: 使用第三方库IQKeyboardManager时会使整个页面上移,导航栏页偏移出了显示范围.在聊天界面就会使得上面的消息看不到. 解决方法: 首先说明:在聊天界面使用IQKeyboardManager这个第三方库无法解决这个问题,至少我没找到解决办法.网上说的那些用ib创建UI,把控制器的view改成scrollview,或纯代码创建UI,重写loadView方法,然后把self.view = scrollview的解决方法会把布局搞乱.没有试,太麻烦.解决思路:在聊天页面禁用IQKey

解决 ios7.0 以后自定义导航栏左边按钮靠右的问题

解决 ios7.0 以后自定义导航栏左边按钮靠右的问题 www.111cn.net 编辑:edit02_lz 来源:转载 最近开发了一个ios的app,在ios7.0+出现自定义导航栏左边按钮出现靠右的情况,后来自己解决了,解决办法如下 1.自定义按钮  代码如下 复制代码 //左按钮UIButton *leftBtn = [[UIButton alloc]initWithFrame:RectWithPara(-20, 0, 44, 44)];[leftBtn addTarget:self ac

实际iOS编程中遇到的自定义导航栏按钮,导致手势返回失效的解决方法

1\在实际编程过程中往往需要自定义导航栏上面的按钮,也就用: - (instancetype)initWithCustomView:(UIView *)customView; 但用了这个方法后可能会导致iOS7,8的手势返回失效,解决方法就是在自定义的导航栏的viewDidLoad方法中添加如下代码 注意:只有用系统的导航栏,或者继承于系统的导航栏才可以用Push方法,并且自带返回手势. - (void)viewDidLoad { [super viewDidLoad]; __weak type

elementUI的导航栏在刷新页面的时候选中状态消失的解决

首先elementUI的导航栏中的选中项的高亮显示时的字体颜色可以在属性中设置,但是高亮时的背景颜色不能设置,所以要自己修改高亮的背景颜色 .el-menu-item.is-active { background-color: #00b4aa !important; } 在使用elementUI构建vue项目的时候会遇到页面刷新的时候子路由会保持原来的,页面中的内容也是当前对应路由的内容,但是elementUI的导航栏中的高亮显示的却失去了原来的状态,以下是我解决这个内容的方法,暂时没有发现什么

今天遇到的问题,解决UITableView页面上面的内容先被导航栏覆盖,然后再向下移动的问题 ( 懒加载有时候并不好! )

今天需要在同事的代码接上接口,但发现一个问题,坐标系发生了改变.一般来说,我们都会现在viewDidLoad中创建UITableView;但是这次我是在viewDidLoad发送Http请求后再创建,然后就导致了请求回来后UITableView页面的内容先整体向上移被导航栏覆盖,然后我移动到我指定的位置,我一直无法,试了又试,后面发现,原因就是我没有在viewDidLoad第一时间创建UITableView,在请求回来再创建,这就导致了UITableView上页面的内容y坐标值由0变成64,造成