9

Edit: I awarded the bounty to john since he put a lot of effort into his answer, and would get it anyways, but there's still no working solution. I am still looking for an answer, if someone knows how to do this it'd be greatly appreciated.

I want to add a "maximize" button to my app that hides the navigation and tab bar. The navbar and tabbar should slide in/out smoothly, and the inner/content view should also expand and shrink at the same rate as the navbar and tabbar.

I used [self.navigationController setNavigationBarHidden: YES/NO animated: YES]; for the navbar and found this thread How to hide uitabbarcontroller for hiding the tabbar.

UITabBar class extension:

- (void) setTabBarHidden:(BOOL)hidden animated:(BOOL)animated {
    CGRect screenRect = [[UIScreen mainScreen] bounds];

    float screenHeight = screenRect.size.height;
    if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
        screenHeight = screenRect.size.width;
    }
    if (!hidden) {
        screenHeight -= self.tabBar.frame.size.height;
    }
    [UIView animateWithDuration: (animated ? UINavigationControllerHideShowBarDuration : 0) animations: ^{
        for (UIView* each in self.view.subviews) {
            if (each == self.tabBar) {
                [each setFrame: CGRectMake(each.frame.origin.x, screenHeight, each.frame.size.width, each.frame.size.height)];
            } else {
                [each setFrame: CGRectMake(each.frame.origin.x, each.frame.origin.y, each.frame.size.width, screenHeight)];
            }
        }
    } completion: ^(BOOL finished) {
        NSLog(@"Animation finished %d", finished);
    }];
}

The problem is when I use the two at the same time (hiding/showing the nav and tab bar), it's not clean. If the navbar comes first, anything anchored to the bottom jumps (see example below), and if the tabbar comes first, the top jumps.

Example: I position the UIButton in the bottom right and set its autoresizing mask

resizeButton.frame = CGRectMake(self.view.bounds.size.width - 50, self.view.bounds.size.height - 100, 32, 32); // hardcoded just for testing purposes
resizeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;

But when the navbar and tabbar are minimized the UIButton jumps between the two states (doesn't slide along with the tab bar). However, if I change it to attach to the top right, it slides perfectly with the nav bar.

Does anyone know how to solve this?


Edit: This is the closet and most elegant solution I have so far (just trying to get a working concept):

[UIView animateWithDuration: UINavigationControllerHideShowBarDuration animations: ^{
    if (self.isMaximized) {
        self.tabBarController.view.frame = CGRectMake(0, 20, screenRect.size.width, screenRect.size.height + 49 - 20);
        [self.navigationController setNavigationBarHidden:YES animated:YES];
    } else {
        self.tabBarController.view.frame = CGRectMake(0, 20, screenRect.size.width, screenRect.size.height - 20);
        [self.navigationController setNavigationBarHidden:NO animated:YES];
    }
} completion: ^(BOOL finished) {
    NSLog(@"Frame done: %@", NSStringFromCGRect(self.view.frame));
    return;
}];

On maximizing:

  • Slides the navbar up, and slides the tabbar down, at the same time
  • The top of the inner/content view slides up, and the bottom of this view jumps down

On minimizing:

  • Slides the navbar down, and slides the tabbar up, at the same time
  • The top of the inner/content view slides down properly, but the bottom jumps to the final value, leaving whitespace which is then covered by the sliding tabbar

If I rearange the order of the minimizing-animations (so the navbar animatino is called first), then the top in the inner/content view jumps

Community
  • 1
  • 1
Raekye
  • 5,081
  • 8
  • 49
  • 74

3 Answers3

7

the solution i use should eliminate the jump problem you see.

this solution is derived from an Objective-C category found Carlos Oliva's github page, and while the copyright in that code is "all rights reserved", i wrote him and he provided permission for use.

my category code varies only slightly from his code. also, find below the category code the invocation code that i use in my app.

from UITabBarController+HideTabBar.m

// the self.view.frame.size.height can't be used directly in isTabBarHidden or
// in setTabBarHidden:animated: because the value may be the rect with a transform.
//
// further, an attempt to use CGSizeApplyAffineTransform() doesn't work because the
// value can produce a negative height.
// cf. http://lists.apple.com/archives/quartz-dev/2007/Aug/msg00047.html
//
// the crux is that CGRects are normalized, CGSizes are not.

- (BOOL)isTabBarHidden {
    CGRect viewFrame = CGRectApplyAffineTransform(self.view.frame, self.view.transform);
    CGRect tabBarFrame = self.tabBar.frame;
    return tabBarFrame.origin.y >= viewFrame.size.height;
}


- (void)setTabBarHidden:(BOOL)hidden {
    [self setTabBarHidden:hidden animated:NO];
}


- (void)setTabBarHidden:(BOOL)hidden animated:(BOOL)animated {
    BOOL isHidden = self.tabBarHidden;
    if (hidden == isHidden)
        return;
    UIView* transitionView = [self.view.subviews objectAtIndex:0];

    if (!transitionView)
    {
#if DEBUG
    NSLog(@"could not get the container view!");
#endif
        return;
    }

    CGRect viewFrame = CGRectApplyAffineTransform(self.view.frame, self.view.transform);
    CGRect tabBarFrame = self.tabBar.frame;
    CGRect containerFrame = transitionView.frame;
    tabBarFrame.origin.y = viewFrame.size.height - (hidden ? 0 : tabBarFrame.size.height);
    containerFrame.size.height = viewFrame.size.height - (hidden ? 0 : tabBarFrame.size.height);
    [UIView animateWithDuration:kAnimationDuration 
                     animations:^{
                         self.tabBar.frame = tabBarFrame;
                         transitionView.frame = containerFrame;
                     }
     ];
}

from my ScrollableDetailImageViewController.m

- (void)setBarsHidden:(BOOL)hidden animated:(BOOL)animated
{
    [self setTabBarHidden:hidden animated:animated];
    [self setStatusBarHidden:hidden animated:animated];

    // must be performed after hiding/showing of statusBar
    [self.navigationController setNavigationBarHidden:hidden animated:animated];
}

- (void)setTabBarHidden:(BOOL)hidden animated:(BOOL)animated
{
    id parent = self.navigationController.parentViewController;
    if ([parent respondsToSelector:@selector(isTabBarHidden)]
        && hidden != [parent isTabBarHidden]
        && [parent respondsToSelector:@selector(setTabBarHidden:animated:)])
        [parent setTabBarHidden:hidden animated:animated];
}
john.k.doe
  • 7,533
  • 2
  • 37
  • 64
  • Thanks, I will try this out. Is `kAnimationDuration` just any duration value? Also, what's the use of `CGRectApplyAffineTransform`? I found documentation here https://developer.apple.com/library/mac/#documentation/graphicsimaging/reference/CGAffineTransform/Reference/reference.html, but when I NSLog the original frame with the affine'd frame, I get the same CGRect – Raekye Jun 15 '13 at 22:41
  • I just tried this, and I get the same result. If the navbar comes first, the top is jumpy. If the tabbar comes first (as you say) the bottom is still jumpy. Also, when I NSLog the regular view frame vs. the affine'd view frame (not changing your code) I get the same values – Raekye Jun 15 '13 at 22:55
  • first, `kAnimationDuration` shouldn't matter, but for my code it is hard-coded to `.2` . second, the `CGRectApplyAffineTransform` also shouldn't matter unless the view.transform is something other than identity. finally, i am curious if your view has any `viewDidLayoutSubviews` calls, or if there might be a view hierarchy that's causing the outer view to be sized with the tab-bar hiding, but doesn't size the inner view at the same time. – john.k.doe Jun 16 '13 at 17:59
  • My View Controller is a subclass of `UIViewController`. Nothing special in it. It is added to a `UINavigationController` with `initWithRootViewController`. Those view controllers are added to a `UITabBarController` with `setViewControllers:(NSArray*)viewControllers`. There are no extra `viewDidLayoutSubviews` calls I'm aware of, besides any that are part of the "default behavior" (I don't use it) – Raekye Jun 16 '13 at 19:10
  • so … in looking at your code just a touch more closely … you might benefit from having the call to `[self.navigationController setNavigationBarHidden:XX animated:YES]` to residing outside of the `[UIView animateWithDuration:animations:completion:]` call … because my sense is that it's trying to do animation on top of animation there. and if your call to the category i included does anything similar, that might also account for the jump. – john.k.doe Jun 16 '13 at 23:00
  • Sorry for the confusion, yeah I have tried with the animations in different orders and in and out of different blocks. The example in the question was one thing that worked; when I tried your answer with the affine stuff I had one call after the other - as in your example – Raekye Jun 16 '13 at 23:27
-1

Just try this code, If it is working. I have written this code a year before. But, still it works good for me.

I didn't used the block based animations. Because it was written when I am new to iOS. Just try and optimize yourself as you wanted.

- (void) hideTabBar:(UITabBarController *) tabbarcontroller {

    [self.navigationController setNavigationBarHidden:YES animated:YES];
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.1];
    for(UIView *view in tabbarcontroller.view.subviews)
    {
        if ([[UIDevice currentDevice]userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
            if([view isKindOfClass:[UITabBar class]])
            {
                [view setFrame:CGRectMake(view.frame.origin.x, 480, view.frame.size.width, view.frame.size.height)];
            } 
            else 
            {
                [view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 480)];
            }            
        }else if([[UIDevice currentDevice]userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
            if([view isKindOfClass:[UITabBar class]])
            {
                [view setFrame:CGRectMake(view.frame.origin.x, 1024, view.frame.size.width, view.frame.size.height)];
            } 
            else
            {
                [view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 1024)];
            }
        }
    }
    [UIView commitAnimations];
}

// Method shows the bottom and top bars

- (void) showTabBar:(UITabBarController *) tabbarcontroller {

    [self.navigationController setNavigationBarHidden:NO animated:YES];
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.1];
    for(UIView *view in tabbarcontroller.view.subviews)
    {
        if ([[UIDevice currentDevice]userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
            if([view isKindOfClass:[UITabBar class]])
            {
                [view setFrame:CGRectMake(view.frame.origin.x, 430, view.frame.size.width, view.frame.size.height)];
            } 
            else 
            {
                [view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 436)];
            }            
        }else if([[UIDevice currentDevice]userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
            if([view isKindOfClass:[UITabBar class]])
            {
                [view setFrame:CGRectMake(view.frame.origin.x, 975, view.frame.size.width, view.frame.size.height)];
            } 
            else 
            {
                [view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 980)];
            }
        }
    }
    [UIView commitAnimations]; 
}
Dinesh Raja
  • 8,501
  • 5
  • 42
  • 81
-2

Try this

you can hide tabbar contrller and navigation bar using animation like:-

-(IBAction)hide:(id)sender
{
[self hideShowBars];
}
- (void) hideShowBars
{
   CGRect rect = self.navigationController.navigationBar.frame;
   CGRect rect1 = self.tabBarController.tabBar.frame;

   if(self.navigationController.navigationBar.isHidden)
    {
      [self.navigationController.navigationBar setHidden:NO];
      rect.origin.y=self.view.frame.origin.y;
    }
   else
   {
      rect.origin.y=self.view.frame.origin.y-rect.size.height;
   }

   if(self.tabBarController.tabBar.isHidden)
   {
      [self.tabBarController.tabBar setHidden:NO];
      rect1.origin.y=self.view.frame.size.height-rect1.size.height-rect1.size.height;
   }
   else
   {
      rect1.origin.y=self.view.frame.size.height;
   }
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.50];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(hideShowBarsAnimationStopped)];

self.navigationController.navigationBar.frame=rect;
self.tabBarController.tabBar.frame = rect1;
[UIView commitAnimations];
 }
- (void) hideShowBarsAnimationStopped
{
if(self.navigationController.navigationBar.frame.origin.y==self.view.frame.origin.y)
    return;

if(!self.navigationController.navigationBar.isHidden)
{
    [self.navigationController.navigationBar setHidden:YES];
}

if(!self.tabBarController.tabBar.isHidden)
{
    [self.tabBarController.tabBar setHidden:YES];
}
}
Ravindhiran
  • 5,304
  • 9
  • 50
  • 82
  • `beginAnimations` \ `commitAnimations`? That way of animating views is long deprecated in favour of block based animations. – Abizern Jun 14 '13 at 08:49
  • Doesn't resize the inner/content view – Raekye Jun 14 '13 at 13:29
  • just i tried navigation bar and tabbar only if you want resize inner/content view refer this code same way try yourself if have a doubt ask i will help you – Ravindhiran Jun 14 '13 at 13:54
  • Yes that's what I'm trying to do (move the nav and tabbar + resize inner content), as it says so in the question and bounty. And you can read in the question I've tried many similar things, which have not worked – Raekye Jun 15 '13 at 22:11