Under the Bridge

Snippet: Full screen presentModalViewController

Today we have a little iPhone user interface programming problem to present to you with its not so self evident solution, courtesy of the kind assistance of one Bryan Henry on the iphonesdk list.

The scenario is, we’ve pushed a pack of controllers onto our UINavigationController as we drill down through the interface, and now we’ve reached a place where the user taps something that we want to enter a full screen display state for momentarily. Well, that’s easy enough, the naïve iPhone programmer thinks to himself, we’ll just hide the status bar then modally present a controller with a full screen view, right?

[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];

FullscreenController *fullscreenController = [[FullscreenController alloc] initWithNibName:@”Fullscreen” bundle:nil];

[self.navigationController presentModalViewController:fullscreenController animated:NO];

[fullscreenController release];

Yes, he would think that. But he WOULD BE WRONG. What actually happens is that the view is presented below the empty space where the status bar used to be. Bah. Fooling with offsets in the “Fullscreen” xib file simply results in the view being clipped by that empty space. Double bah. Thankfully, a plaintive plea to the list produces in short order an explanation:
Your problem is that the UITransitionView that is used to animate in   
your modal view has clipsToBounds=YES, so any drawing that should   
happen in the rect where the status bar would be doesn’t happen since   
the UITransitionView’s bounds don’t extend there. I ran into this just   
the other day. The (somewhat hackish but completely safe) solution was   
to set clipsToBounds=NO for the UITransitionView whenever its set as   
the superview (or rather, whenever your view is added as a subview of   
the UITransitionView). 
Well, that was somewhat less than intuitive, wasn’t it now? So what you need is a patched UIView class,

@interface NoClipModalView : UIView

}

@end 

@implementation NoClipModalView

- (void)didMoveToSuperview

   self.superview.clipsToBounds = NO

@end 

and then in your controller, rather than having it actually load the nib, you override -loadView to have it create one of those, and then create your fullscreen views inside it.

- (void)loadView

{

   // instead of calling [super loadView], which doesn’t account for a hidden status bar, create an unclipped view here

   CGSize screenSize = [UIScreen mainScreen].bounds.size;

   CGRect screenBounds = CGRectMake(0, -20, screenSize.width, screenSize.height);

   NoClipModalView *sView = [[NoClipModalView alloc] initWithFrame:screenBounds]; 

   self.view = sView; 

   [sView release];

   // and then manually create the view(s) you want inside it.

   TWGestureImageView *newImageView = [[[TWGestureImageView alloc] initWithFrame:screenBounds] autorelease];

   self.imageView = newImageView;

   [self.view addSubview:self.imageView];

}

 And that, dear friends, is how to create a full screen modal display. Reverse it from where you created the FullscreenController up at the top with the expected

   [self.navigationController dismissModalViewControllerAnimated:NO];

   [[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO];

and th-th-that’s all folks!
6
  • http://www.benzado.com/ Benjamin Ragheb

    Too much work!

    Once you have created the NoClipModalView class, all you need to do is go to Interface Builder and change the class used of the root view from UIView to NoClipModalView. There’s no need to create the view hierarchy manually.

    However, this solution seems a little bit hacky to me, since you’re making assumptions about the view hierarchy created by presentModalViewController:. It seems like it would be safer to create a new UIWindow over the current one; this is apparently what the MoviePlayerController does.

  • http://www.alexcurylo.com/ Alex

    Thanks for the input Benjamin — it seems like you should be right about being able to use Interface Builder, but in fooling around with the offsets in the xib file, I couldn’t get it to load as intended. *shrug* If this comes up again and I have more than one view in the intended modal display, I’ll take another stab at it.

    As for the hackiness … I don’t really think it’s a problem. We’re using the defined interface of self.view in the -loadView method, after all, and for that to stop being supported would imply repercussions far wider than this little status bar clipping workaround stopping to work.

  • http://www.benzado.com/ Benjamin Ragheb

    You are using the documented API; I wasn’t suggesting that clipsToBounds would stop being supported. However, you are making assumptions about how presentModalViewController: constructs the view hierarchy, and that’s not documented anywhere and is subject to change.

    It’s a low-risk solution, because if there is a change your app won’t break, at worst the black bar will return. But it still makes assumptions that undocumented behavior will not change.

  • http://www.alexcurylo.com Alex

    Fair enough. However, if -presentModalViewController is ever changed, I’m pretty darn confident that the changes will include exposing an easier way to accomplish this with it.

    Come to think of it, I should file a Radar asking for exactly that and see if they do suggest a supported alternative. Good reminder Benjamin, thanks!

  • http://vectorvector.tumblr.com Cory

    Call this some time before presentModalViewController.
    [myViewController setWantsFullScreenLayout:YES];
    This fixes the white space issue for me, without a UIView subclass.

  • How about solution for presentModalViewController Page Sheet & MoviePlayer in fullscreen

    How about code workaround for presentModalViewController Page Sheet & MoviePlayer in fullscreen with a transparent background.