Question

I have a UIViewController — let's call it "FormController" — which is simply a form that edits an object. I want to use it in 2 different situations:

  1. Creating a new object — using using UINavigationController's presentModalViewController: method.

  2. Editing an existing object — push the view controller onto the UINavigationController stack, not using a dialog method.

There is a slight difference in that in the modal situation I would like to have a toolbar with "Cancel" and "Done" buttons, whereas in the stack situation I would like to just have the navigation bar provided by the UINavigationController.

This would be similar to the Contacts application where the "New Contact" and the "Edit Contact" screens seem to use the same view controller, but the New Contact form is presented modally while the Edit screen is pushed onto the navigation stack.

My question is: What is the best way to handle both situations without having to write 2 separate, but mostly identical view controllers?

I thought about creating a "ModalFormController" which encapsulates the bare "FormController" through composition and adds a toolbar, but I read somewhere in the docs that Apple doesn't recommend nesting view controllers.

Was it helpful?

Solution

What I do (sometimes) is set up an enum that specifies the type of the view controller.

For example, you might have two types: an Edit type, and an Add ("new") type.

The Add type is implemented via a modal view controller, while the Edit type is pushed onto an existing navigation stack.

In the view controller's -viewDidLoad: method, I simply do a switch/case tree that sets up the title and other appearance characteristics depending on the type enumeration specified above.

The nice thing about this is that it is easy to add a new type. The downside is that the conditional tree for handing this enumeration can get complicated quickly, depending on how different the types are.

But the switch/case tree makes it a lot easier to manage.

So, it depends on what you're trying to do with the two types. But it's definitely doable.

OTHER TIPS

Why not use subclassing? Make ModalCreateFormController a subclass of EditFormController and handle the modal-specific stuff in the subclass.

In addition to having an explicit property on the view controller (as Alex Reynolds suggests), two other approaches that occur to me are:

  1. If you have some kind of model object that you're editing, ask it for its current state. If it's ever been saved, then you're in edit mode. Otherwise, you're in create mode.

  2. Look at the value of the controller's parentViewController property. If it's an instance of UINavigationController, then you're in the navigation stack. If you're being displayed modally, it'll be an instance of your list controller.

Ug, I hate extra ivars…

I use this instead:

if([[self.navigationController viewControllers] objectAtIndex:0] == self){

        //Modal

    }else{

        //Pushed

    }

It is a bit of a hack, but we are using the logic that if the offending view controller is the first in the stack, you can't go back. Actually we are ignoring the fact of whether it is modally displayed at all.

I had to do this a bunch of times in my app and after trying a couple different ways of doing it, including modal subclasses & a re-usable modal helper classes that used forwardInvocation. I found the best pattern was to make a containingModalViewController method each view controllers that (usually) creates and returns a UINavigationController for the caller to use with presentModalViewController.

In most cases this method builds and returns a UINavigationController with self as the root view controller (with repeated calls to the method checking self.navigationController and returning that instead if it's not nil). Other cases I made a dummy root controller first and pushed self on second in order to get a back button. Then a trick can be used to catch the back button press: http://smallduck.wordpress.com/2010/10/05/intercepting-uinavigationcontroller/

In some cases the view doesn't need a navigation bar and so this method just adjusts some flags and returns self. I even found in some cases that did need a navigation bar it was simpler to make that method invoke self.view, then tweak the view hierarchy to add a UINavigationBar and again return self. But in any case, the setup often isolated to that one method, and the caller handles it the same in each case.

Apple explains how the contacts application works under the hood:

To allow a custom view controller class to be used to both display and edit content, override the setEditing:animated: method.

You get some functionality for free, e.g. Edit/Done button.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top