6
votes

I have a view controller which is not fullscreen (has a status bar) and want to present a modal view controller which is fullscreen.

If I hide the status bar at the beginning of the animation (parent's viewWillDisappear or modal's viewWillAppear) then for a moment the parent will be visible without a status bar, looking like a bug.

If I do it at the end of the animation (parent's viewDidDisappear or modal's viewDidAppear) then the status bar will be visible for a moment over the modal view, i.e. it won't appear as the modal view "covered it".

Is there a way to do this nicely?

edit:

One possibility would be to create a UIWindow with windowLevel=alert for at least the duration of the animation. The sample iAd ad seems to cover the status bar nicely without another window, so it must be possible somehow.

5
I think that your UIWindow solution is probably the right way to go. We use similar techniques in our apps when we need unusual transitions between view controllers. - Jesse Rusak

5 Answers

4
votes

Another fun little project. This was the best I could come up with. It's not too bad if you don't mind using your own container controller to manage presenting/dismissing view controllers. I try to do things in a general way but this could be rolled into an app w/ the ContainerViewController if desired.

Note that I only implemented the equivalent of UIModalTransitionStyleCoverVertical. You can customize the animation however you like as well.

Relevant animation code:

- (void)presentViewController:(UIViewController *)viewControllerToPresent
{   
    // do nothing if no controller
    if (!viewControllerToPresent) return;

    [__viewControllers addObject:viewControllerToPresent];
    CGRect toFrame = viewControllerToPresent.view.frame;
    toFrame.origin = CGPointMake(0, CGRectGetMaxY(self.view.bounds));
    viewControllerToPresent.view.frame = toFrame;

    [UIView transitionWithView:self.view
                      duration:0.2
                       options:UIViewAnimationOptionTransitionNone
                    animations:^{
                        [[UIApplication sharedApplication] setStatusBarHidden:viewControllerToPresent.wantsFullScreenLayout withAnimation:UIStatusBarAnimationSlide];
                        [self.view addSubview:viewControllerToPresent.view];
                        viewControllerToPresent.view.frame = [UIScreen mainScreen].applicationFrame;
                    }
                    completion:nil];
}

- (void)dismissViewController
{
    // nothing to dismiss if showing first controller
    if (__viewControllers.count <= 1) return;

    UIViewController *currentViewController = [__viewControllers lastObject];
    UIViewController *previousViewController = [__viewControllers objectAtIndex:__viewControllers.count - 2];

    [UIView transitionWithView:self.view
                      duration:0.2
                       options:UIViewAnimationOptionTransitionNone
                    animations:^{
                        [[UIApplication sharedApplication] setStatusBarHidden:previousViewController.wantsFullScreenLayout withAnimation:UIStatusBarAnimationSlide];
                        CGRect toFrame = currentViewController.view.frame;
                        toFrame.origin = CGPointMake(0, CGRectGetMaxY(self.view.bounds));
                        currentViewController.view.frame = toFrame;
                    }
                    completion:^(BOOL finished) {
                        [currentViewController.view removeFromSuperview];
                        [__viewControllers removeLastObject];
                    }];
}
0
votes

I do that in my app with this code:

[[UIApplication sharedApplication] setStatusBarStyle: UIStatusBarStyleBlackOpaque];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation: UIStatusBarAnimationSlide ];


DocumentListViewController * dl = [[DocumentListViewController alloc] initWithNibName:@"DocumentListView" bundle:nil] ;
UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:dl];
[dl release];

// Go to the list of documents...
[[self.view superview] addSubview:nav.view];

nav.view.alpha = 0.0 ;

[self hideActivityAlert];

[UIView animateWithDuration:1.0 animations:^{
    nav.view.alpha = 1.0; } completion:^(BOOL A){
        [self.view removeFromSuperview];
        [self release];} ];

The status bar is presented shoftly while the animation occurs.

You have to be sure that the first view, when status bar is going hidden will fill the space. Use the property autoresizingMask with proper value.

0
votes

Here's a solution that seems to work. You can derive the viewcontroller you want to present modally from my TSFullScreenModalViewController, or you can just implement the code right in the view controller itself.

@interface TSFullScreenModalViewController : UIViewController
{
    UIWindow*   _window;
}

- (void) presentFullScreenModal;

@end

@implementation TSFullScreenModalViewController

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

    [_window resignKeyWindow];
    [_window release];
    _window = nil;
}

- (void) presentFullScreenModal
{
    UIViewController* rvc = [[UIViewController new] autorelease];
    rvc.view.backgroundColor = [UIColor clearColor];

    _window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds] ;
    _window.windowLevel = UIWindowLevelStatusBar+1;
    _window.backgroundColor = [UIColor clearColor];
    _window.rootViewController = rvc;
    [_window makeKeyAndVisible];

    [UIApplication sharedApplication].statusBarHidden = YES;
    [rvc presentModalViewController: self animated: YES];
    [UIApplication sharedApplication].statusBarHidden = NO;
}

@end

Derive your modal view controller, like this:

@interface MyModalViewController : TSFullScreenModalViewController
{
}

- (IBAction) onDismiss:(id)sender;

@end

Use it from another view controller, like this:

- (IBAction) onShowModal:(id)sender
{
    MyModalViewController* mmvc = [[MyModalViewController new] autorelease];
    [mmvc presentFullScreenModal];
}

Finally, dismiss your view controller as you normally would:

- (IBAction) onDismiss:(id)sender
{
    [self dismissModalViewControllerAnimated: YES];
}
0
votes

Might be a bit of a hack but have you considered:

  1. Take a screenshot programatically of the first view with the status bar (see this SO question)
  2. Create a new view which displays the image you just took in fullscreen (using UIImage's initWithFrame)
  3. Hide the status bar
  4. Present the modal view controller

Then to dismiss the modal view, just reverse the steps.

EDIT:

Won't work for this because you can't take screenshots of the status bar.

0
votes

It could be as simple as delaying the presentation of your modalViewController using performSelector:withDelay:

Tell the status bar to animate out and then launch the modal controller with the right delay so it coincides with the status bar animation.