恢复iOS7之前的UINavigationController pushViewController动画

38

最近开始将我的 iOS 代码迁移到 iOS7,但遇到了一些问题。

我有一个 UINavigationController,其中包含子视图控制器,并使用 pushViewController 来显示下一个视图。为了创建一组图像的视差动画,我对 UINavigationController 进行了自定义,使其动画化一组 UIImageViews,而我的子视图控制器都具有 self.backgroundColor = [UIColor clearColor] 的透明度。

由于 iOS7 更新了 UINavController 动画它的子视图控制器的方式,通过部分移动当前视图控制器并在其上推送新的视图控制器,导致我的视差动画看起来很糟糕。我看到之前的视图控制器稍微移动了一下,然后消失了。有没有办法恢复以前的 UINavigationController 的 pushViewController 动画呢?我似乎在代码中找不到这个选项。

WelcomeLoginViewController* welcomeLoginViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"WelcomeLogin"];
    [self.navigationController pushViewController:welcomeLoginViewController animated:YES];    

甚至尝试使用:

    [UIView animateWithDuration:0.75
                     animations:^{
                         [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                         [self.navigationController pushViewController:welcomeLoginViewController animated:NO];
                         [UIView setAnimationTransition:<specific_animation_form> forView:self.navigationController.view cache:NO];
                     }];

有人知道答案吗?


2
记录一下,现在的库存动画(即只是简单的推送,带有animated:YES参数)展现了相同的行为。 - skantner
10个回答

46

我成功地通过为 UINavigationController 创建一个类别来解决了新的过渡类型问题。在我的情况下,我需要将其恢复到旧的转换样式,因为我有透明的视图控制器滑过静态背景。

  • UINavigationController+Retro.h

    @interface UINavigationController (Retro)
    
    - (void)pushViewControllerRetro:(UIViewController *)viewController;
    - (void)popViewControllerRetro;
    
    @end
    
  • UINavigationController+Retro.m

    #import "UINavigationController+Retro.h"
    
    @implementation UINavigationController (Retro)
    
    - (void)pushViewControllerRetro:(UIViewController *)viewController {
        CATransition *transition = [CATransition animation];
        transition.duration = 0.25;
        transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        transition.type = kCATransitionPush;
        transition.subtype = kCATransitionFromRight;
        [self.view.layer addAnimation:transition forKey:nil];
    
        [self pushViewController:viewController animated:NO];
    }
    
    - (void)popViewControllerRetro {
        CATransition *transition = [CATransition animation];
        transition.duration = 0.25;
        transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        transition.type = kCATransitionPush;
        transition.subtype = kCATransitionFromLeft;
        [self.view.layer addAnimation:transition forKey:nil];
    
        [self popViewControllerAnimated:NO];
    }
    
    @end
    

嗨,Arne,感谢您的解决方案。但是您动画化了UINavigationController的视图而不是控制器的子视图。这样,导航栏表现出不同的行为。尝试通过创建完全自定义的两个视图的动画来解决问题,但仍然没有得到期望的效果。 - Bart van den Berg
谢谢,这对我有用。然而,当用户点击返回按钮时,它会恢复到破碎的动画(因为返回按钮不调用popViewControllerRetro)。 - Bill
Bill,你可以用leftBarButtonItem替换返回按钮。 我选择了transition.duration = 0.4,因为在Xcode模拟器上它似乎更接近旧速度。 - jcsahnwaldt Reinstate Monica
这对我很有帮助 - 我也有一个透明背景的视图控制器被推到具有透明背景和背景图像的 NavigationController 上。新的 iOS7 转换看起来很糟糕,我想知道为什么苹果不能在不强制更改本地 UI 行为的情况下“悄悄地”添加这些新功能 - 这给我们所有人带来了很多麻烦和头痛! - Miros
这对我非常有效。 我曾经为我的视图控制器使用clearColor(为了在UIWindow子视图下获得平滑的动画),但在实施这个方法之前遇到了令人讨厌的随机卡顿。 - weienw
显示剩余6条评论

22

我也遇到了相同的问题,即背景颜色不清晰且动画效果差,因此我使用新的iOS7 API为ViewController创建自定义过渡效果。你只需要为导航控制器设置一个委托即可:

// NavigationController does not retain delegate, so you should hold it.
self.navigationController.delegate = self.navigationTransitioningDelegate;

只需将此文件添加到您的项目中:MGNavigationTransitioningDelegate


惊人!最佳解决方案 - Gerardo
太棒了!运行得很顺利!谢谢! - jestro
非常有用!谢谢! - Sagar D
很不幸,这个解决方案会破坏自动布局 - 当你旋转并返回时,前一页的视图都会损坏,直到你再次旋转。 - Tom
1
好的解决方案。我制作了你的Gist的Swift 3版本:https://gist.github.com/steryokhin/f136bd3e0c38b0fcdbff30b311714257 - Sergey Teryokhin
@Tom 或许这会有所帮助 https://gist.github.com/ArtFeel/7690431#gistcomment-1912196 - ArtFeel

18

我遇到一个问题,当UIViewController A通过pushViewController推送UIViewController B时,推送动画会在25%左右停止,然后停顿,最后将B滑入。

iOS 6上没有出现这种情况,但是一旦我开始在XCode 5中使用iOS 7作为基本的SDK时,就开始出现了。

解决方法是视图控制器B的根视图(根视图是viewController.view的值,通常在loadView中设置)没有设置backgroundColor。在该根视图的初始化程序中设置一个backgroundColor可以解决该问题。

我按以下方式修复了此问题:

// CASE 1: The root view for a UIViewController subclass that had a halting animation

- (id)initWithFrame:(CGRect)frame

{

     if ((self = [super initWithFrame:frame])) {

          // Do some initialization ...

          // self.backgroundColor was NOT being set

          // and animation in pushViewController was slow and stopped at 25% and paused

     }

     return self;

}

// CASE 2: HERE IS THE FIX

- (id)initWithFrame:(CGRect)frame

{

     if ((self = [super initWithFrame:frame])) {

          // Do some initialization ...

          // Set self.backgroundColor for the fix!

          // and animation in pushViewController is no longer slow and and no longer stopped at 25% and paused

          self.backgroundColor = [UIColor whiteColor]; // or some other non-clear color

     }

     return self;

}

我遇到了完全相同的问题,但不幸的是我的视图B有一个clearColor,我只需要添加与self.parentViewController.view.xxxxxx完全相同的图像背景,现在它可以工作了...谢谢! - mongeta
是的,这正好解决了问题。谢谢! - Bill Cheswick
疯狂的解决方案。对我也起作用了! - CedricSoubrie
你是怎么做到的?我本来可以试试巫术的..谢谢 - amar

7

首先,我不使用Storyboard。我尝试使用UINavigationController+Retro。由于某些原因,UINavigationController很难释放堆栈顶部的UIViewController。以下是适用于我使用iOS 7自定义转换的解决方案。

  1. Set delegate to self.

    navigationController.delegate = self;
    
  2. Declare this UINavigationControllerDelegate.

    - (id<UIViewControllerAnimatedTransitioning>)navigationController (UINavigationController *)navigationController 
    animationControllerForOperation:(UINavigationControllerOperation)operation
    fromViewController:(UIViewController *)fromVC 
    toViewController:(UIViewController *)toVC 
    {
        TransitionAnimator *animator = [TransitionAnimator new]; 
        animator.presenting = YES;
        return animator;
    }
    

    Note that it'll only get called when animated is set to YES. For example

    [navigationController pushViewController:currentViewController animated:YES];
    
  3. Create the animator class extending NSObject. I called mine TransitionAnimator, which was modified from TeehanLax's TLTransitionAnimator inside UIViewController-Transitions-Example.

    TransitionAnimator.h

    #import <Foundation/Foundation.h>
    @interface TransitionAnimator : NSObject <UIViewControllerAnimatedTransitioning>
    @property (nonatomic, assign, getter = isPresenting) BOOL presenting;
    @end
    

    TransitionAnimator.m

    #import "TransitionAnimator.h"
    @implementation TransitionAnimator
    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
        return 0.5f;
    }
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
        UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];    
        if (self.presenting) {
            //ANIMATE VC ENTERING FROM THE RIGHT SIDE OF THE SCREEN
            [transitionContext.containerView addSubview:fromVC.view];
            [transitionContext.containerView addSubview:toVC.view];     
            toVC.view.frame = CGRectMake(0, 0, 2*APP_W0, APP_H0); //SET ORIGINAL POSITION toVC OFF TO THE RIGHT                
            [UIView animateWithDuration:[self transitionDuration:transitionContext] 
                animations:^{
                    fromVC.view.frame = CGRectMake(0, (-1)*APP_W0, APP_W0, APP_H0); //MOVE fromVC OFF TO THE LEFT
                    toVC.view.frame = CGRectMake(0, 0, APP_W0, APP_H0); //ANIMATE toVC IN TO OCCUPY THE SCREEN
                } completion:^(BOOL finished) {
                    [transitionContext completeTransition:YES];
                }];
        }else{
            //ANIMATE VC EXITING TO THE RIGHT SIDE OF THE SCREEN
        }
    }
    @end
    

    Use presenting flag to set the direction you want to animate or which ever condition you prefer. Here's the link to Apple reference.


好的。有一件事是不需要使用表现变量(presenting variable)。UINavigationControllerOperation表示这个操作是Push还是Pop。 - ttotto

2

只需添加以下内容:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

这是一个段落:
[[self window] setBackgroundColor:[UIColor whiteColor]];

最终结果:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions (NSDictionary *)launchOptions {    
    [[self window] setBackgroundColor:[UIColor whiteColor]];

    // Override point for customization after application launch.
    return YES;
}

2
感谢大家的反馈。我找到了一个解决方案,完全重新创建UINavigationController的行为。当我快完成时,我遇到了Nick Lockwood的解决方案:https://github.com/nicklockwood/OSNavigationController
OSNavigationController是一个开源的UINavigationController重新实现。它目前仅具有UINavigationController功能的子集,但长期目标是复制100%的功能。
OSNavigationController实际上并不打算直接使用。想法是您可以派生它,然后轻松自定义其外观和行为,以满足您的应用程序可能具有的任何特殊要求。由于代码是公开的,您无需担心私有方法,未记录的行为或版本之间的实现更改,因此自定义OSNavigationController比尝试自定义UINavigationController要简单得多。
通过使用他的代码覆盖我的UINavigationController,我能够在UINavigationcontrollers中使用背景图像。
谢谢!

1

3
除非我错了,否则这似乎只适用于呈现视图控制器,当将视图控制器推入导航控制器时,我无法使其正常工作。 - GenesisST

1

Arne的答案的Swift 5实现:

extension UINavigationController {
  func pushViewControllerLegacyTransition(_ viewController: UIViewController) {
    let transition = CATransition()
    transition.duration = 0.25
    transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
    transition.type = .push
    transition.subtype = .fromRight
    view.layer.add(transition, forKey: nil)
    pushViewController(viewController, animated: false)
  }

  func popViewControllerLegacyTransition() {
    let transition = CATransition()
    transition.duration = 0.25
    transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
    transition.type = .push
    transition.subtype = .fromLeft
    view.layer.add(transition, forKey: nil)
    popViewController(animated: false)
  }
}

0

我投票支持@Arne的答案,因为我认为这是解决这个问题最优雅的方法。我只想添加一些代码来回应@Bill对@Arne解决方案的评论中提出的问题。以下是评论的引用:

谢谢,这对我很有用。 然而,当用户点击返回按钮时,它会恢复到不正常的动画(因为返回按钮不调用popViewControllerRetro)- Bill Oct 3 at 12:36

为了在按下返回按钮时调用popViewControllerRetro,您可以执行一个小技巧以实现此目的。 进入您的推送视图控制器,导入UIViewController + Retro.h,并将此代码添加到您的viewWillDisappear方法中:

- (void)viewWillDisappear:(BOOL)animated {
    if ([self.navigationController.viewControllers indexOfObject:self] == NSNotFound) {
        [self.navigationController popViewControllerRetro];
    }

    [super viewWillDisappear:animated];
}

这个if语句将检测后退按钮被按下,并从类别类中调用popViewControllerRetro。

最好的问候。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接