8

I am building an app that uses multiple types of screens--all which warrant their own custom view controllers. I am successfully switching between view controllers and their related views by reassigning the main window's rootViewController with a method in my app delegate like the following:

- (void)changeRootViewController:(NSString *)controllerName
{
    if (controllerName == @"book") {
        rootViewController = (UIViewController *)[[BookViewController alloc] init];
        [self.window setRootViewController:rootViewController];
    } else if (controllerName == @"something_else") {
        // Use a different VC as roowViewController
    }
}

The way that I am doing this seems like it just can't be best practice, however. I don't want to use a UINavigationController or a UITabBarController as the rootViewController, either. Is this the wrong way to be doing this, and if so, how should I be approaching this differently?

I thought this would have been covered somewhere, but (I feel as if) I've Googled the heck out of it, looked for related questions, etc. Sorry if I've missed something!

GarlicFries
  • 8,095
  • 5
  • 36
  • 53
  • y u dont want to use navigation controller – Amit Singh Jul 25 '11 at 14:08
  • what is there in navigation controller that is not according to ur requirement – Amit Singh Jul 25 '11 at 14:09
  • 1
    For a couple of reasons. First, the "path" through the application is not linear--don't want to be pushing and popping controllers all over the place. Second, if I push a new view controller onto the stack, are the resources used by controllers lower in the stack still being used? I can't find an answer for this, but do not want this to occur. – GarlicFries Jul 25 '11 at 14:10
  • 1
    Furthermore, I don't _want_ to use a `UINavigationController`. I'd also simply like to understand how to do this without it. – GarlicFries Jul 25 '11 at 14:15
  • @AmitSingh the reason I am looking a UINavigationController free solution is that after login the controller I want to push is UITabBarController, but everyone know UITabBarController can't be pushed in UINavigationController. Any solutions? – S.J Jul 12 '13 at 06:31
  • does the controllerName == @"book" comparison work? I find it difficult to believe. – Paul de Lange Aug 27 '13 at 12:48
  • @Paul de Lange, it did--at least two years ago. Not sure if it still would... – GarlicFries Aug 27 '13 at 17:22
  • In the latest Xcode this will have a warning "undefined behavior" because it is wrong. Use -isEqual: instead. I'm pretty sure it was never correct. – Paul de Lange Aug 28 '13 at 11:40
  • I hate warnings and won't settle for an approach if it means swallowing them. This did work, indeed--sorry, bud. Good to know not to do it now, though! – GarlicFries Aug 28 '13 at 15:43

2 Answers2

10

One great way to do this is by using iOS5+'s UIViewController's ability to have child UIViewControllers (it's called view controller containment). I certainly had a difficult time figuring out how to do this until I watched the WWDC video that explains this in good detail.

In a nutshell, it allows you to create your own parent view controller that owns a series of child view controllers. This single parent view controller can (and probably should, unless you're doing some really fancy stuff :P) sit as your app's window's root view controller. This method of having a single view controller act as a parent (and facilitate the adding, removing, and transitioning of the child view controllers) is reminiscent of what UINavigationController does (which is Apple's intent). Now you can create your own UINavigationController-like parent view controller, but have totally different transition animations and UI.

As an example, in the parent view controller, in the viewDidLoad, I add the first child controller like this:

self.currentlyDisplayedChildViewController = [[TheFirstViewController alloc] init];
[self addChildViewController:self.currentlyDisplayedChildViewController];
[self.view addSubview:self.currentlyDisplayedChildViewController.view];
[self.currentlyDisplayedChildViewController didMoveToParentViewController:self];

Then I would have a function to do the transition to the next child view controller (NOTE: this function belongs in the parent view controller -- the view controller that's going to act as your UINavigationController):

- (void)transitionToViewController:(UIViewController *)nextChildViewController
{
    [self addChildViewController:nextChildViewController];
    __weak TheParentViewController *me = self;
    [self transitionFromViewController:self.currentlyDisplayedChildViewController
                      toViewController:nextChildViewController
                              duration:1.0f
                               options:UIViewAnimationOptionTransitionFlipFromLeft
                            animations:nil
                            completion:^(BOOL finished)
                            {
                                [nextChildViewController didMoveToParentViewController:self];
                                [me.currentlyDisplayedChildViewController willMoveToParentViewController:nil];
                                [me.currentlyDisplayedChildViewController removeFromParentViewController];
                                me.currentlyDisplayedChildViewController = nextChildViewController;
                            }];
}

One thing really nice is you can use all the standard UIViewAnimationTransition options (or define your own custom animation in the animations block. Additionally, any orientation rotations events are automatically forwarded from the parent view controller to the child view controllers. This was one of that hairiest problems with doing custom root view controller manipulations on your own.

I would suggest taking a look at WWDC2011 video titled "Implementing UIViewController Containment".

Mr. T
  • 12,795
  • 5
  • 39
  • 47
3

Its not a bad solution. You basically set one view as the root view. When you need another UIViewController you set another one. Just be careful for the leaks...

  • Create the rootViewController as property of the class with retain.
  • Before this:

rootViewController = (UIViewController *)[[BookViewController alloc] init];

Add this:

if(rootViewController){
    self.rootViewController=nil;
}

}

So you release the previous one.

Edit 1: One thing: my explanation here is based on the fact that you don't want to use an UINavigationController.

Vikas S Singh
  • 1,748
  • 1
  • 14
  • 29
Rui Peres
  • 25,741
  • 9
  • 87
  • 137
  • Thanks. Is there a more commonly used or better practice for doing this, though? – GarlicFries Jul 25 '11 at 18:42
  • 1
    It depends of the architecture of your app Brennon. There is always a better solution. The only thing we can do, is to learn every time we make an application and hope to create a better one next time. – Rui Peres Jul 25 '11 at 22:26
  • 2
    Hmm...always feels strange to accept an answer that basically says, "What you're already doing is just fine...and although there is a better solution, I'm not going to tell you what it is..." – GarlicFries Aug 08 '11 at 21:18