Semi-Modal (Transparent) Dialogs on the iPhone
Popping up a modal dialog on the iPhone is a fairly straightforward process:
modalDialogViewController *modalController = [[modalDialogViewController alloc] initWithNibName:@"modalDialogView" bundle:nil]; [self presentModalViewController:modalController animated:YES]; [modalController release];
Dismissing it then is a simple matter of the modalController invoking:
[self dismissModalViewControllerAnimated:YES];
But what if you want to show only half a page’s worth or maybe you need the underlying view to continue being available for user viewing or interaction. Or maybe you want to show a pop-up toolbar where users are asked to choose something before continuing. You might think “Aha! I’ll just have my modal dialog view be half as tall and make the background transparent.”
Go ahead, give it a try. We’ll wait… (* … the girl from Ipanema goes walking … *)
So now you know that the standard modal dialog can only be full-screen and maintains a solid black background. What’s more you can’t interact with what’s behind it because, you know it’s modal — and modal means users shouldn’t be able to do anything else until they’re done with the front-most task (unless you’re the search box in the Contacts app in which case apparently it’s OK to be kindasortamodal).
So what we’re going to do is have a view that can be modal but takes only part of the top view, the space above it remaining visible. What’s more, you can choose to have it so tapping on the background view hides the modal view, or even go full-bore and let the background remain responsive to user input. This technically makes the view semi-modal so let’s ignore the sirens and the UI Police banging on the door and go with that.
The first thing you need is a view that has something interactive on it. The easiest way to build one is in interface builder, so go ahead and make yourself one. For the sake of expedience make it only a fraction of the screen. Here’s an example of a half-height view along with some user controls. The background is set to fully transparent. The view is connected to a UIViewController that reacts to user input:

In this case, the view is the same height as the whole screen because we want the upper portion to be see-through but not react to user input. If we wanted it to be truly interactive, we could make the view height be as tall as the actual content (i.e. half-screen) but that would make it a bit strange for the user because it would be hard to tell apart the actual content from the modal view. But hey, it’s your app. You can do what you want. Another option is to set the background of this view black and partially transparent. That would look cool and show a nice smoky cover while we’re in modal mode… unless you’re mucking with color (like we are in this example) in which case it’s best to leave it fully transparent. Next throw the following code in the parent UIViewController. Load up the UIViewcontroller/UIView you just created and pass the view to this routine instead of calling the standard presentModalViewController method (substitute your application delegate for MyAppDelegate):
// Use this to show the modal view (pops-up from the bottom)
- (void) showModal:(UIView*) modalView { UIWindow* mainWindow = (((MyAppDelegate*) [UIApplication sharedApplication].delegate).window); CGPoint middleCenter = modalView.center; CGSize offSize = [UIScreen mainScreen].bounds.size; CGPoint offScreenCenter = CGPointMake(offSize.width / 2.0, offSize.height * 1.5); modalView.center = offScreenCenter; // we start off-screen [mainWindow addSubview:modalView]; // Show it with a transition effect [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.7]; // animation duration in seconds modalView.center = middleCenter;
[UIView commitAnimations]; }
What this does is add your view as a top-level above the main window, effectively rendering it modal. It also uses Core Animation to move the window from offscreen bottom up until it’s fully shown. You should adjust the timing to suit your view’s actual height. I’ve found that the taller the semi-modal view, the more time you should give it to become fully visible. Now let’s go through the hiding action. Note that we use the animation completion handler to do the actual removing of the item from the parent view and cleaning up. We also use the context parameter of the animation call (which was thoughtfully provided for exactly this sort of thing) to keep track of what view to clean up afterward:
// Use this to slide the semi-modal view back down. - (void) hideModal:(UIView*) modalView { CGSize offSize = [UIScreen mainScreen].bounds.size; CGPoint offScreenCenter = CGPointMake(offSize.width / 2.0, offSize.height * 1.5); [UIView beginAnimations:nil context:modalView]; [UIView setAnimationDuration:0.7]; [UIView setAnimationDelegate:self]; [UIView setAnimationDidStopSelector:@selector(hideModalEnded:finished:context:)]; modalView.center = offScreenCenter; [UIView commitAnimations]; } - (void) hideModalEnded:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { UIView* modalView = (UIView *)context; [modalView removeFromSuperview]; }
[ Update: As noted in the comments, there was an extra release on modalView in hideModalEnded. This code was excerpted from a larger code-base and the release was left in inadvertently. The code listing here has been updated. Thanks for catching it, folks. ]
What I’m not showing you here is the way to trigger the show/hide action. That’s entirely up to you. In standard iPhone modal dialogs this is often a button in the toolbar or navigation bar. In the case of the semi-modal dialog, however, you have even more flexibility. Basically it comes down to the show/hide elements being:
- Explicit: Provide an Accept or Cancel button on your view.
- Implicit: You can simulate an action toolbar that shows and hides this way. Put a row of buttons on the view and wire it so tapping each one invokes
hideModalbefore going on to the actual action. - Other: Tapping anywhere else on the screen dismisses the dialog. You can do this by placing a full-screen sized view (or custom transparent button) behind your modal dialog and wiring it so it a tap-down action dismisses the dialog . For best results, try making this full-screen view black and semi-transparent (e.g. opacity=0.2). This way the user’s main view darkens so they get a sense your modal dialog is in focus but they still get to see what’s behind.
Here’s a movie of the above semi-modal view in action. It lets the user select a color then confirm or cancel the action. The modal view in this case also has interactive controls on it. As the user changes color the background image changes in real-time so they can visualize what the end-result will be like. Once they’re done they can tap the checkbox or X/cancel buttons to make the modal go away.
The semi-modal dialog is a handy UI interaction component but it’s important to think about how to dismiss the dialog and what to allow in the rest of the visible region on the main window to avoid confusing the user. Also note that there are no restrictions on the shape or size of the overlay view as long as the background color is set to [UIColor clearColor]. You can use the same method for irregularly shaped pop-ups.
Go nuts and have fun.
[Update: Nathan in the comments below has posted some code on github. You may want to check it out. ]
ramin
Leave a comment


ramin-
This is a great tutorial, thanks much. I’m an iPhone newbie and I’m failing to get this to work- when I run it with a view that otherwise works with ‘presentModalViewController’, I get no exceptions, but the view never displays (though it does initialize, I’ve verified that in the debugger). My one difference is that my main view controller is a UITabBarController. I’ve experimented with various combinations but can’t come up with anything that actually shows the view.
Any general thoughts would be appreciated.
Thanks,
Ken
Ken
October 19, 2009
10/10. Great tutorial
Vish
October 22, 2009
im confused, how go you call this from the ibaction of a button?
dep
November 12, 2009
@ken You’ll want to put the showModal/hideModal routines inside your UITabBarController-derived class then pass them the view that has the stuff you want to show (which itself could be lazy-loaded from a nib file).
The showModal code adds the semi-modal view on top of the main window so it should show up on top of everything else. It shouldn’t make any difference what’s underneath it.
Hope this helps.
@dep To invoke this you’ll need an IBAction method called, say, “showPanel” in your UIViewController-derived class. Wire the button to the showPanel routine. Inside showPanel you would load up your view and call the showModal method and pass it along. Wire up the close button to a “closePanel” IBAction method that calls the hidePanel method.
ramin
November 12, 2009
Great tutorial. Thanks! I was stuck on the modal view taking up the entire screen. This fixes the problem nicely.
Richard Monson-Haefel
November 18, 2009
How would I call this from the -(void) viewdidload method?
Jason
November 28, 2009
I was wondering if you had the project files for this cause i am a Newbie and really want to add this effect. Thanks in advance.
Jason
November 29, 2009
very nice, exactly what I was looking for
but I am messing around with that transparency stuff: if I set the view alpha of the view below 1, all elements on it will also be semi-transparent. what am I doing wrong??
jotwee
January 29, 2010
btw:
if you want to flip it up and down and up again, you have to reset the center in -(void)hideModalEnded:
- (void) hideModalEnded:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
UIView* modalView = (UIView *)context;
[modalView removeFromSuperview];
CGSize offSize = [UIScreen mainScreen].bounds.size;
CGPoint middleCenter = CGPointMake(offSize.width / 2.0, offSize.height / 2.0);
modalView.center = middleCenter;
[modalView release];
}
jotwee
January 29, 2010
@Jason: It should be pretty straightforward to use. Just create a class derived from UIViewController, add the above methods to it and then call those methods to show and hide the view as a modal.
@jotwee: To adjust only the transparency of the background, try setting the alpha of the background view to < 1.0 *before* adding any subviews to it.
On flipping up/down/up: not sure why you’d want to do that, because if you’re holding a reference to the modalView so it can be reused later, the ‘showModal’ routine sets the center before it starts off. And if you do it in the hideModalEnded after it’s been removed from the subview then it won’t be visible. Maybe I’m not getting your usage scenario.
ramin
February 2, 2010
@ramin
??
first time show: middleCenter = middle of the screen
second time show: middleCenter = offScreenCenter
after hiding, you have to reset the center (when the view is not visible)
jotwee
February 24, 2010
Interessting tut, but i have problems understanding the way it works.
i am using a tabController in the mainView and derived from UIViewController a window wich should act as an InfoWindow.
Tapping a TableCell should raise the InfoWindow.
exactly the UIView parameter is confusing me….
would you please give me the source to learn from it?
regards!
martin
March 17, 2010
Hi Ramin,
Thanks for posting the tutorial. I’m having a problem dismissing the modal.
I loaded the model from a Xib as you suggested.
Do you mind posting the entire project? Thanks
Moe
March 24, 2010
Great article first.
Regarding “adjust only the transparency of the background, try setting the alpha of the background view to < 1.0 *before* adding any subviews to it”
I tried to do this but it doesn’t work. Even tried setting the alpha and opaque properties after view added to my fullscreen view that has a semi-transparent background.
Anyone have any ideas how to make the background semi-transparent as suggested in the article.
Thanks again.
Peter
April 22, 2010
Also, regarding “if you want to flip it up and down and up again, you have to reset the center in -(void)hideModalEnded:”
I added the extra code at the end of each animation cycle – I’m guessing jotwee has the same use-case as me – my view is wrapped in a view controller.. I keep a reference to it (and it’s view) hanging around, so seeming need to do the extra reseting each time, at the of each flip ‘down’.
Peter
April 23, 2010
Great article first.
Regarding “adjust only the transparency of the background, try setting the alpha of the background view to < 1.0 *before* adding any subviews to it"
I tried to do this but it doesn't work. Even tried setting the alpha and opaque properties after view added to my fullscreen view that has a semi-transparent background.
Anyone have any ideas how to make the background semi-transparent as suggested in the article.
Thanks again.
Steve
May 27, 2010
I’ve been trying to isolate this problem for days. Your code works great presenting the modal partially overlaying the content, but the controller for that modally presented view causes a crash whenever an IBAction, even an empty one, is called. The console specifies the crash as “unrecognized selector sent to instance.” This same code works fine if the modal is called and dismissed the standard way.
I’ve tried to recreate the modal interface from scratch with just your code and basic methods to call it. Even this stripped down example crashes the same way. Click “+” to present the modal, then click “Done” and the app hangs, then crashes. You can download the project here: http://dl.dropbox.com/u/416448/Test.zip
I’m sure I’m missing something simple here. Any help at all would be greatly appreciated.
Christian
June 3, 2010
Someone helped me at Stack Overflow: http://stackoverflow.com/questions/2969662/iphone-unrecognized-selector-sent-to-instance-error
Basically, I released the view variable too soon.
I knew it was simple!
Christian
June 3, 2010
Nice code sample! However, there seems to be an error in hideModalEnded:finished:context:
modalView SHOULD NOT BE RELEASED! It is released automatically by calling removeFromSuperView. Se the docs.
Releasing it *might* crash your app, or it might not, so it’s difficult to track down.
Victor Widell
June 16, 2010
Thanks for the example I was looking for info on how to do something like this. I was able to adapt this technique to my use case with minimal problems as you explained it with enough detail. Good Job !!!
jon
June 16, 2010
Thanks for the tutorial; this solved a problem I was working to figure out! Though one point of note. In the hideModalEnded function, calling [modalView removeFromSuperview] followed by
[modalView release] may very well give you a EXEC_BAD_ACCESS error as removeFromSuperview will set modalView equal to nil effectively setting the retainCount to zero and destroying the object. Again thank you for taking the time to post this tutorial, it saved me a chunk of time.
Nathan
June 20, 2010
Can this work for a horizontal view? I tried it and it is always portrait view even when I set the animation transition style like this:
[UIView setAnimationTransition:UIModalTransitionStyleFlipHorizontal forView:modalView cache:YES];
Nathan
August 3, 2010
Could you post a link to the download of this? Thanks
AD
August 17, 2010
Hi,
Very useful but I have a little problem when my view contains an TabBarController…. When I push the modal popup, the bottom of the TabBarController is still displayed…..
Any help ?
Thanks
Stan
August 20, 2010
Thanks, this is a really good example. Have you modified this to be able to rotate the device and have the views rotate?
Nick
September 10, 2010
Thanks for this great post. It really very helpful.
kch14ng
September 14, 2010
I’m struggling to figure out how to wire a button on the modal view to dismiss it. It seems like you’d need to use delegation in order to call hideModal from the modal view’s controller.
Steve
September 21, 2010
To prevent use of a singleton, and having a reference to your application delegate around in your function.
You might want to just use:
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
onedayitwillmake
September 27, 2010
Crap, this does not rotate!
mark
October 9, 2010
I used this idea and turned it into a few classes to present Semi-Modal dialogs in an easy way.
The code is up on github, see this post here: http://bit.ly/9HqgKV
Nathan
October 18, 2010
There’s a problem when the view is added on a rotated device with autorotation allowed. The fix is… unpleasant. It involves applying a transform to the view and then if you want it to slide in from a specific direction, you need to mess with the offScreenCenter based on which way the the device is oriented.
spstanley
November 21, 2010
Thanks for the link Nathan. Good work!
ramin
December 30, 2010
@nick It shouldn’t be too hard to set up the class as an observer for interface rotation notification via something like:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification object:nil];
Then set the
transformangle of the view to rotate accordingly.ramin
December 30, 2010
@stan: To get it to show above everything else the semi-modal dialog needs to be added as a child of the top-level window and moved in front of all other children of the window (including tabbars and navigation controllers).
ramin
December 30, 2010
@AD: I was trying to describe a general technique instead of a specific type of modal window. You may want to take a look at Nick’s post at http://bit.ly/9HqgKV for some code examples.
ramin
December 30, 2010
@nathan It’s only for horizontal view but it should be easy to catch the orientation change event and set the transform on the modal view to have it rotate. See my other comment for one way to watch for orientation change. This way it will work even if the modal view is not part of a UIViewController chain. You’ll want to remember to remove it as an observer when dismissing the modal.
ramin
December 30, 2010
this works great, but I am not familiar with transform to get this to work in landscape mode. could anybody point me in the right direction? thanks.
david
July 8, 2011
Great job dude!
just for who is reading this post, for me it worked better this way:
- (void) showModal:(UIView*) modalView {
CGSize offSize = self.view.frame.size;
CGPoint offScreenCenter = CGPointMake(offSize.width / 2.0, offSize.height * 1.5);
CGPoint realCenter = CGPointMake(offSize.width / 2.0, offSize.height / 2.0);
modalView.center = offScreenCenter;
[self.view addSubview:modalView];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.7];
modalView.center = realCenter;
[UIView commitAnimations];
}
and in the modalView:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
-(void) orientationChanged:(NSNotification *)notification {
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation == UIDeviceOrientationLandscapeLeft ||
orientation == UIDeviceOrientationLandscapeRight)
self.view.frame = CGRectMake(0, 110, 1024, 599); // the size that you want
else
self.view.frame = CGRectMake(0, 280, 768, 599);// the size that you want
}
- (void)viewDidUnload {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
[super viewDidUnload];
}
Crystian
August 21, 2011