73

I'm updating my app for iOS 7 and I discovered a weird problem. I'm presenting a UIViewController wrapped in a UINavigationController with UIModalTransitionStyleFlipHorizontal.

In iOS 6 it works fine, but in iOS 7 the navigation bar bounces after the transition. Does this have something to do with the status bar? I've set translucency of the main navigation bar to NO.

In the Info.plist, View controller-based status bar appearance is set to NO.

And here is a GIF showing the problem in a minimal demo app:

enter image description here

Here is my code:

feedNavigationController = [[UINavigationController alloc] init];
feedNavigationController.navigationBar.translucent = NO;

SettingsViewController *settingsVC = [[SettingsViewController alloc] init];

feedNavigationController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[feedNavigationController setViewControllers:[NSArray arrayWithObjects:settingsVC, nil]];

[self presentViewController:feedNavigationController animated:YES completion:nil];
Ortwin Gentz
  • 52,648
  • 24
  • 135
  • 213
Rene
  • 731
  • 6
  • 4
  • Unfortunately the video cannot be found. – Tim Bodeit Sep 13 '13 at 15:12
  • Same issue exactly, the GM build has some Biig issues, collectionView reloadData is one too! – jasonIM Sep 15 '13 at 16:12
  • I've added a GIF to the question showing the problem in a minimal demo app. I already recorded it to ask the same question, but then I found out I wasn't the first with this problem :) – Jonathan Mar 02 '14 at 14:01
  • FYI: this bug has been **fixed in iOS 8**. – Jonathan Sep 27 '14 at 21:55
  • 1
    Not for me! iOS 8 still has this problem. self.navigationController?.navigationBar.layer.removeAllAnimations() in viewwillappear fixed it for me. – nmdias Mar 03 '15 at 17:36
  • 1
    Same in my project, on iOS9 I use UIView transitionFromView:toView:duration:options:completion: to switch between two viewcontrollers and facing this bug. Following answers solved problem with navigation bar height, but not with it's tint color, anyone faced this problem? – eagle.dan.1349 Oct 07 '15 at 08:22

6 Answers6

54

This appears to be a UIKit bug. The following workaround seems to resolve the issue for me.

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    [self.navigationController.navigationBar.layer removeAllAnimations];
}

(Place this in the view controller you are transitioning to).

Ben Packard
  • 26,102
  • 25
  • 102
  • 183
  • This worked for me. Make sure to do it in the root view controller of your navigation controller, not the navigation controller itself. – alexcash Sep 21 '13 at 05:57
  • Interesting - what exactly happens on dismissal? – Ben Packard Sep 23 '13 at 16:05
  • The same thing happens on dismissal. So this only partially solves the problem. Adding this to the view controller that began the transition (the from view controller) doesn't fix the issue when dismissing and transitioning back. – Philip Sep 24 '13 at 22:43
  • I see - I flip from a controller without a navbar, so never encountered this. Technically it resolves the stated question ;) – Ben Packard Sep 25 '13 at 02:00
  • It worked for me!Wonderful! But when dismiss the viewController,the viewController we are back to is jump a bit. – HamasN Sep 26 '13 at 12:03
  • I have reported this to Apple as a bug, with the proposed solution - as a pointer to where the bug might be. But I'm still trying to solve the dismissal problem myself, with no luck. – daniel.gindi Oct 08 '13 at 12:29
  • it does not works when dismissing the controller, the nav bar still bounces....Apple sucks.. – flypig Oct 18 '13 at 10:18
  • 5
    This worked for me the *first* time the animation is called, but not in any subsequent calls. Why would this be, since it's called every time the view will appear??? Maddening. – Matt Miller Oct 23 '13 at 15:10
  • Matt, I had the same problem. For whatever reason I solved it by creating the flipped view controller via storyboard not manually. The reason probably is that I assigned the flipped view controller to a property and recycled it whereas with storyboarding it's created freshly every time. – Ortwin Gentz Oct 26 '13 at 23:51
  • dismiss have the same problem. – user501836 Jan 15 '14 at 09:54
  • i am switching between one view with no navigation controller and one view with a navigation controller. i too experienced the "dismiss" problem but fixed it when I reversed the presentation logic (that is, which controller was doing the presenting and which controller was doing the dismissing). – Archie1986 Feb 13 '14 at 20:55
  • Apparently this bug still exists in iOS 9.1 and I'm glad this workaround still resolves the issue. – René Nov 02 '15 at 20:40
16

To solve this problem for present & dismiss, I use the iOS7 custom transition.

Add this to your UIViewController :

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    return (id<UIViewControllerAnimatedTransitioning>)self;
}

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    return (id<UIViewControllerAnimatedTransitioning>)self;
}

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return 0.7f;
}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    UIView *containerView = [transitionContext containerView];


    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    [containerView addSubview:fromVC.view];

    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    [containerView addSubview:toVC.view];

    UIViewAnimationOptions animationOption = ([toVC.presentedViewController isEqual:fromVC])?UIViewAnimationOptionTransitionFlipFromLeft:UIViewAnimationOptionTransitionFlipFromRight;


    [UIView transitionFromView:fromVC.view
                        toView:toVC.view
                      duration:[self transitionDuration:transitionContext]
                       options:animationOption
                    completion:^(BOOL finished) {
                        [transitionContext completeTransition:YES];
                    }];
}

To use it, you just had to check if you are on iOS7 and set the transitionDelegate :

YourVCWithTheCustomTransition* yourVC = [[YourVCWithTheCustomTransition alloc] init];

CGFloat deviceVersion = [UIDevice currentDevice].systemVersion.floatValue;
if(deviceVersion >= 7.0) [yourVC setTransitioningDelegate:yourVC];

[self presentModalViewController:yourVC animated:YES];
[yourVC release];

In my case, I had a custom UINavigationController where the custom transition is defined : i don't have to do this each time.

mluisbrown
  • 14,448
  • 7
  • 58
  • 86
EricD
  • 587
  • 6
  • 22
  • This worked beautifully with the addition of "else yourVC.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;" in the test of deviceVersion so it continues to work in iOS6. – LavaSlider Dec 02 '13 at 14:46
  • This should be the accepted answer. Works perfectly. My only suggestion would be to put this code in a separate class which you can then use in any View Controller. – mluisbrown Mar 06 '14 at 22:41
  • 3
    [Don't check for the system version](http://triplesoftware.nl/2014/02/checking-device-or-system-version/) but check whether the method is availbe: `if ([yourVC respondsToSelector:@selector(setTransitioningDelegate:)] { [yourVC setTransitioningDelegate:yourVC]; }` – rckoenes Mar 19 '14 at 14:13
  • Great it works, only additional thing I had to do was set the delegate in 'viewwillappear' since some of my views have segues defined via the storyboard editor. – Imran Mar 30 '14 at 12:00
  • Funny enough, returning from this transition can cause a permanent white screen under iOS 8. Of course you don't need to do any of this under iOS 8 because the bug that made this necessary was fixed. – user564904 Nov 03 '14 at 18:42
9

This appears to be a UIKit bug. The following workaround seems to resolve the issue for me.

presentViewController (place this in the view controller you are transitioning to):

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    [self.navigationController.navigationBar.layer removeAllAnimations];
}

dismissViewControllerAnimated (place this in the view controller you dismiss to):

- (void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    [self.navigationController.navigationBar.layer removeAllAnimations];
}

if you don't use autolayout. you need add this to the view controller you dismiss to:

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];

    [self.view setNeedsLayout];
} 
Rishil Patel
  • 1,977
  • 3
  • 14
  • 30
Hugo
  • 1,122
  • 11
  • 12
3

I had the same issue and could "solve it" (it's not a real solution to the problem but it looks fine :) ). The trick is present the view controller using pushViewController/popViewController with an UIView animation to make a flip. Here is a example code to present the view controller:

UIViewController *viewController = [[UIViewController alloc] init];
[UIView transitionWithView:self.navigationController.view 
                  duration:0.5 
                   options:UIViewAnimationOptionTransitionFlipFromLeft 
                animations:^{
                   [self.navigationController pushViewController:viewController animated:NO];
                }
                completion:nil];

To dismiss it:

[UIView transitionWithView:self.navigationController.view 
                  duration:0.5 
                   options:UIViewAnimationOptionTransitionFlipFromRight 
                animations:^{
                   [self.navigationController popViewControllerAnimated:NO];
                }
                completion:nil];

If you don't want the navigationBar on the pushed controller just call [self.navigationController setNavigationBarHidden:YES animated:NO] in viewWillAppear. I hope this approach help you.

Rishil Patel
  • 1,977
  • 3
  • 14
  • 30
RemeR
  • 667
  • 8
  • 20
  • 1
    This actually solves the problem of both sides of the transition, which is not something the above answer does. If you have a navbar on the modal controller, you might need to set the left bar button item to nil to get the exact effect you had on iOS6(no back button) but that's about it. – entropy Oct 03 '13 at 08:10
  • 1
    By the way, for some reason, this method results in visual glitches in the navbar(though not as severe as not using it). Mainly, the title and back button text don't show up until after the transition and are blank during. – entropy Oct 04 '13 at 13:55
  • This should be the accepted answer, it's a perfect workaround for transitioning in both directions. just check hides bottom bar on push and hide/unhide navigation bar as needed in the view will appear method of the pushing and pushed controller. – raladdin Nov 15 '13 at 09:11
1

For both the presenting and the presented view controller I have a UITableViewController within UINavigationController, both configured using Auto Layout. I noticed that the other answers didn't solve the problem that on dismiss the tableView of the presenting view controller jumps 20 pt vertically.

This solution addresses this problem.

In the presented view controller (as proposed by Ben Packard):

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.navigationController.navigationBar.layer removeAllAnimations];
}

In the presenting view controller (as proposed in part by dusty):

- (void)viewWillLayoutSubviews{
    if (self.navigationController.presentedViewController) {
        [self.navigationController.navigationBar.layer removeAllAnimations];
        [self.tableView.layer removeAllAnimations];
    }
    [super viewWillLayoutSubviews];
}
Ortwin Gentz
  • 52,648
  • 24
  • 135
  • 213
-2

The same for me. What actually worked is to change style to CoverVertical, looks much smoother.

titicaca
  • 813
  • 1
  • 11
  • 20