A Short Note About Modal Presentation

Dave Smith
Dave Smith

iOS5 Update

New in iOS5, the use and meaning of many of the existing UIViewController properties has changed as part of the new container view controller framework that is being made available. As a result, some of the information in this article is no longer valid. In particular, the parentViewController property is no longer populated on a presented modal view controller, making the method suggested here to dismiss that way ineffective. Instead, applications should use the presentingViewController property to dismiss from within the presented context in iOS5, such as:

[[self presentingViewController] dismissModalViewControllerAnimated:YES];

There is also a new version of these methods that takes a completion block (presentViewController:animated:completion: and dismissViewControllerAnimated:completion:), which is the overall recommended method to use in the future.

One of the most common design patterns used to transition from one view to another in iOS is the use of the modalViewController property of UIViewController. New views are presented using the presentModalViewController:animated: method, and dismissed using the dismissModalViewControllerAnimated: method. Presenting a new view as a modal view does a number of things that are not always obvious:

  • Creates a parent/child relationship between the two view controller objects
  • The new view controller object is retained by the parent
  • Displays the new view fullscreen (on iPhone), optionally in an animated fashion
  • If animated, the animation is taken from the UIModalTransitionStyle of the NEW view

I have highlighted the top two items because it is those two that lead to the primary issues of misuse with modal presentation: over retention and child dismissal.

Child Dismissal

Per the Apple documentation:

The parent view controller is responsible for dismissing the modal view controller it presented using the presentModalViewController:animated: method. If you call this method on the modal view controller itself, however, the modal view controller automatically forwards the message to its parent view controller.

Apple has done the developer a disservice here by automatically correcting bad practice, but doing so doesn't make it good practice. Apple clearly states that presentation AND dismissal of modal views are the responsibility of the parent view controller. dismissModalViewControllerAnimated: should never be called from the child (modal) view; despite the fact that iOS helps you out and autoforwards the request to the parent. The reason is because the second sentence is ONLY true when the modal view is the final child of a modal view stack. If a UIViewController has a modalViewController set, then a dismiss message will result in that controller dismissing its child.

Every view controller that has been presented in this way (it is a child) has a property called parentViewController that is set, making it very easy for the developer to pass the dismiss message on to the proper object, even if they are not in that context at the moment. See the example below, where versions of the right and wrong way to dismiss from a child are displayed:

@implementation ViewControllerOne
- (void)showNewView {
  ViewControllerTwo *controller = [[ViewControllerTwo alloc] init];
  [self presentModalViewController:controller animated:YES];
  [controller release];
}

- (void)dismissRight {
  //Work is done...dismiss the child
  [self dismissModalViewControllerAnimated:YES];
}
@end

@implementation ViewControllerTwo
- (void)dismissWrong {
  //Work is done...dismiss me
  [self dismissModalViewControllerAnimated:YES];
}

- (void)dismissRight {
  //Work is done...dismiss me
  [[self parentViewController] dismissModalViewControllerAnimated:YES];
}
@end

In this example, both places where dismissRight exists are proper ways of dismissing a modal, whether from the parent or child. Using the technique in dismissWrong will only get you into trouble some day.

Consider a case where a developer makes several calls to presentModalViewController:animated:, creating a view controller stack. Every view controller in that stack has their parentViewController and modalViewController properties set...except for the top-level and bottom-level controllers, whom have the parentViewController and modalViewController properties set to nil respectively. If a developer has the improper concept in their heads of where to send a dismiss message, messages sent to view controllers in the middle of the stack (which is allowed) would dismiss the child of the receiver, instead of the receiver itself. This would lead to major troubles in debugging because the design concept was built on flawed logic.

JUST DON'T DO IT.

Over Retention

The other common problem arises from the fact that developers may not realize that presenting a modal view controller (through the act of setting it as the modalViewController property) causes it to be retained by the parent. Therefore, you do not need to create properties in your parent classes to hold instances of the modal:

@interface MyViewController : UIViewController {
  ChildViewController *modalVC;
}
@end

@implementation MyViewController
- (void)viewDidLoad {
  [super viewDidLoad];
  modalVC = [[ChildViewController alloc] init];
}

- (void)showNewView {
  [self presentModalViewController:modalVC animated:YES];
}
@end

In fact, this will keep the object from being released once dismissed, and you will be holding data from one use of the modal to the next. If you want to make sure your modal view controller loads up fresh with each presentation, rely only on the retain message sent by the parent:

@implementation MyViewController
- (void)showNewView {
  ChildViewController *modalVC = [[ChildViewController alloc] init];
  [self presentModalViewController:modalVC animated:YES];
  [modalVC release];
}
@end