Skip navigation

pushViewController versus presentModalViewController

8391 Views 5 Replies Latest reply: Oct 26, 2010 1:44 AM by RayNewbie RSS
jarceneaux Calculating status...
Currently Being Moderated
Oct 19, 2010 3:32 PM
I have been going through this tutorial from icodeblog on moving to another view:

http://icodeblog.com/2008/08/03/iphone-programming-tutorial-transitioning-betwee n-views/

and it uses the following to bring up my new view (indicated by newViewController):

[self.navigationController pushViewController:self.newViewController animated:YES];

This, however, doesn't work for me - nothing happens, and viewDidLoad in the newViewController is not called. If I switch to this statement:

[self presentModalViewController:newViewController animated:YES];

Then it does work (although there's no back button). Can anyone explain what's going on?

Also, I'm wondering how it knows which nib file to load (the correct one is displayed).

Thanks,
Joe
iPhone 4 (simulator), iOS 4
  • RayNewbie Level 5 Level 5 (6,810 points)
    Hi Joe, and welcome to the Dev Forum!
    jarceneaux wrote:
    ... it uses the following to bring up my new view (indicated by newViewController):

    [self.navigationController pushViewController:self.newViewController animated:YES];

    The above is the correct way to add another view controller to the navigation controller's "stack", which will result in a transition to the new controller's view. However two key ingredients need to be in place before that code will do anything:

    1) The current view controller must be on the nav controller's stack. This means that at some point, perhaps when the main xib file was loaded, a nav controller object was created along with a "root" view controller at the base of the stack. The code in question must be in the root controller or some other controller which has been added to the stack, perhaps just above the root controller or higher.

    2) You must have created a view controller object referred to as newViewController. I'm going to assume you did that, since you successfully brought up newViewController as a modal view controller. That wouldn't have worked unless newViewController pointed to a valid view controller object.
    If I switch to this statement:

    [self presentModalViewController:newViewController animated:YES];

    Then it does work (although there's no back button). Can anyone explain what's going on?

    Switching in a modal view controller is quite different from pushing a new controller onto the stack of a nav controller. For this discussion, the most important difference is that step 1, above, isn't necessary. In fact it doesn't matter how the current controller's view was switched in, so long as you create a new controller (step 2) the current controller can present that controller as a modal controller.

    Another difference between a navigation transition and a modal transition is the default "back button". If you expect to ever return from a modal view, you need to add your own back button. In fact, I'm sure you noticed the difference is more than just the back button. Added to the view of every controller on a nav stack (by default) is a "navigation bar", and displayed on the bar is a "navigation item" which can contain a right and left bar button and a title in the center (you can also program custom variations of the nav item's contents). By default, all but the root nav item will be provided with a standard back button (unless the previous nav item has no title). But if you don't want that back button, you can remove it, or replace it with something else.
    Also, I'm wondering how it knows which nib file to load (the correct one is displayed).

    As discussed, at some point before you attempt either type of transition, you must have created a new view controller. If you made a xib file for the new controller, you can create the controller using the [initWithNibName:bundle:|http://developer.apple.com/library/ios/documentation/U IKit/Reference/UIViewControllerClass/Reference/Reference.html#//appleref/doc/uid/TP40006926-CH3-SW15] method of UIViewController:

    My2ndControllerClass *newViewController = [[My2ndControllerClass alloc]
    initWithNibName:@"My2ndControllerClass" bundle:nil];

    That init method knows a special trick that can be confusing. From the doc (see the above link):
    If you specify nil for the nibName parameter and do not override the loadView method in your custom subclass, the default view controller behavior is to look for a nib file whose name (without the .nib extension) matches the name of your view controller class.

    For example, if the tutorial you were following took advantage of this trick, the name of the nib (or xib) would never appear in your code (in which case we might observe the author saved a few key strokes at the expense of his or her students).

    Hope that helps!
    - Ray
    iMac, Mac OS X (10.5.8)
  • RayNewbie Level 5 Level 5 (6,810 points)
    jarceneaux wrote:
    It seems to me that perhaps the problem is that (from what I can tell from the online class reference) a simple UIViewController doesn't have a pushViewController method, whereas a UITableViewController does (calling pushViewController from there works). Does that seem correct?

    No. I think you're still missing the most important actor in this scenario. Neither UIViewController nor UITableViewController has a [pushViewController::|http://developer.apple.com/library/ios/documentation/UIKi t/Reference/UINavigationControllerClass/Reference/Reference.html#//appleref/doc/uid/TP40006934-CH3-SW2] method. That method belongs to UINavigationController. Unless a nav controller has been created and initialized with a root view controller, there's no stack on which to push a view controller and no object which will respond to pushViewController::.

    You might have assumed a UITableViewController object was needed because of example code that only creates a nav controller when the root controller will be a table view controller. In fact, I think the only Xcode template which includes a nav controller demonstrates how it's used with a table view. So in every case where you've gotten pushViewController:: to work, you've called it from a table view controller. But in those cases your success wasn't due to the table view controller. It was because there was always a nav controller behind that table view controller. You may not have noticed where the nav controller was created, since it was probably defined in MainWindow.xib, and created when that nib was loaded at startup.

    Another point to remember is that every descendant of UIViewController has a property named 'navigationController'. That property points to the navigation controller which is controlling the viewController (i.e. the nav controller onto whose stack the view controller was pushed). _If the view controller has not been pushed onto a nav controllers stack, the 'navigationController' property will be nil._ With that in mind let's add some logging to the code you posted last time:
    jarceneaux wrote:
    ... it uses the following to bring up my new view (indicated by newViewController):

    NSLog(@"self.navigationController=%@", self.navigationController); // <-- add this line
    [self.navigationController pushViewController:self.newViewController animated:YES];

    After adding the above log statement, you'll see a description of the parent nav controller in your Console log. But if the current view controller isn't a child of a nav controller (i.e. isn't on the stack of a nav controller), you'll see "self.navigationController=(null)". In that case you'll next send pushViewController:: to a nil pointer, and nothing will happen.

    You might notice something else each time the above code works: A navigation bar across the top of the currently visible view. The nav bar is displayed on top of any view when that view's controller is on the stack of a nav controller (there's an option to hide the bar, but it's unlikely that was turned on in the tutorial you were following).

    Here's an example app that makes the nav controller and all its children in code, so you don't need to refer to any xib files to see what's going on. No table view controller is involved:

    // JoeAppDelegate.h
    @interface JoeAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    UINavigationController *myNavController;
    }
    @property (nonatomic, retain) IBOutlet UIWindow *window;
    @property (nonatomic, retain) UINavigationController *myNavController;
    @end

    // JoeAppDelegate.m
    #import "JoeAppDelegate.h"
    #import "MyViewController.h"
    @implementation JoeAppDelegate
    @synthesize window, myNavController;

    - (void)applicationDidFinishLaunching:(UIApplication *)application {
    // create the root view controller
    MyViewController *rootViewController = [[MyViewController alloc] init];
    // create the nav controller with the root controller at the base of its stack
    UINavigationController *navController = [[UINavigationController alloc]
    initWithRootViewController:rootViewController];
    // retain the nav controller and save its address in the instanc variable
    self.myNavController = navController;
    // reduce the nav controller's retain count to 1
    [navController release];
    // add the nav controller's view to the window
    [window addSubview:self.myNavController.view];

    [window makeKeyAndVisible];
    }

    - (void)dealloc {
    [myNavController release];
    [window release];
    [super dealloc];
    }
    @end

    // MyViewController.h
    @interface MyViewController : UIViewController {
    }
    @end

    // MyViewController.m
    #import "MyViewController.h"
    @implementation MyViewController

    - (void)viewDidLoad {
    [super viewDidLoad];
    // add a "Next" button to the nav bar and connect it to the 'push' method
    UIBarButtonItem *nextButton = [[UIBarButtonItem alloc] initWithTitle:@"Next"
    style:UIBarButtonItemStylePlain target:self action:@selector(push)];
    self.navigationItem.rightBarButtonItem = nextButton;
    [nextButton release];

    // * optional steps that might add clarity to this demo:
    // set the title which will appear on the navigation bar
    int stackLevel = [self.navigationController.viewControllers count];
    NSString *title = [NSString stringWithFormat:@"View %d", stackLevel];
    self.navigationItem.title = title;
    // also set the background of each view to a different color
    self.view.backgroundColor = [UIColor colorWithHue:(((stackLevel-1)*40)%256)/255.0
    saturation:1.0 brightness:1.0 alpha:1.0];
    }

    - (IBAction)push {
    // create a new view controller
    MyViewController *nextViewController = [[MyViewController alloc] init];
    // push the new controller onto the nav controller's stack
    [self.navigationController pushViewController:nextViewController animated:YES];
    // reduce the retain count of the new view controller to 1
    // (because now it's also been retained by the nav controller)
    [nextViewController release];
    }

    - (void)dealloc {
    NSLog(@"%s", _func_);
    [super dealloc];
    }
    @end

    To build the example app, start a new project (File>New Project...) named Joe with the Window-based App template, and add a new class (File>New File>UIViewController subclass) named MyViewController. Then replace the contents of the four class files by pasting the code for each file directly from the forum. You won't need to open Interface Builder; i.e., you don't need to touch MainWindow.xib, and you don't need to make any other xib files.

    For extra credit, replace the lines which create the nav controller with code that just creates the root view controller and adds its view to the window. Hint: Make a rounded rect button in the viewDidLoad method of MyViewController instead of a bar button item since there won't be any navigation bar.

    - Ray
    iMac, Mac OS X (10.5.8)
  • RayNewbie Level 5 Level 5 (6,810 points)
    jarceneaux wrote:
    If instead of pushing self.navController we push navController, the app crashes. ... If they're the same value, why does one crash and the other doesn't?

    This apparent contradiction is one of the most confusing features of Obj-C, especially for programmers migrating from other languages. In most modern, widely used languages, foo.bar (pronounced "foo dot bar") would be the value of the bar element of the structure foo. In fact this would be true in Obj-C as well, if foo were a simple C structure. But when foo points to an Obj-C object, foo.bar means something that may or may not be what you expect:

    int x;

    // the next two statements are equivalent to each other:
    x = foo.bar;
    x = [foo bar]; // 'bar' is called the "getter" method for the property bar

    // these next two statemets are also equivalent to each other:
    foo.bar = x;
    [foo setBar:x]; // 'setBar:' is called the "setter" method for the property bar

    // we might have declared the getter and setter methods in the interface like this:
    - (int)bar;
    - (void)setBar:(int)value;

    // but this declaration does what the two above do, and provides other info as well:
    @property (nonatomic, assign) int bar;

    The property declaration in the example has the "assign" attribute, which means both the getter and setter methods will be equivalent to a direct assignment; i.e. x=bar and bar=x respectively.

    Now let's look at the property declaration for 'myNavController' from the example I posted last time:

    // JoeAppDelegate.h
    @interface JoeAppDelegate : NSObject <UIApplicationDelegate> {
    // ...
    UINavigationController *myNavController;
    }
    // ...
    @property (nonatomic, retain) UINavigationController *myNavController;
    @end

    In this case, the "retain" attribute is included. That means the setter will look something like this:

    - (void)setMyNavController:(UINavigationController*)newController {
    if (myNavController != newController) {
    [myNavController release];
    myNavController = newController;
    [myNavController retain];
    }
    }

    So what's the difference between (1) myNavController=navController and (2) self.myNavController=navController?

    No. 1 just assigns the address of navController to the myNavController instance variable. No. 2 does what no. 1 does, but it also releases the old nav controller (if any) and retains the new nav controller (if any). Now take another look at the code block in which the new controller was created:
     
    - (void)applicationDidFinishLaunching:(UIApplication *)application {
    // ...
    UINavigationController *navController =
    [[UINavigationController alloc] navController // 1) navController retain count is 1
    initWithRootViewController:rootViewController];

    self.myNavController = navController; // 2) navController retain count is 2

    [navController release]; // 3) navController retain count is 1
    // ...
    }

    The above manages memory correctly since it leaves the nav controller with a retain count of 1. We want that, since we expect the nav controller to be freed the next time it receives a release message. But what happens if we remove "self." from the left side of line 2?

    - (void)applicationDidFinishLaunching:(UIApplication *)application {
    // ...
    UINavigationController *navController =
    [[UINavigationController alloc] navController // 1) navController retain count is 1
    initWithRootViewController:rootViewController];

    /* self. */ myNavController = navController; // 2) navController retain count is 1

    [navController release]; // 3) navController retain count is 0
    // ...
    }

    The direct assignment in line 2 doesn't call setMyNavController, so the retain count is unchanged. The count then goes to zero in line 3, which causes the nav controller object to be freed. The next message we attempt to send to the nav controller (e.g. pushViewController:animated:) will be sent to its former address, which is now invalid ("Postmaster: Return to sender. Addressee is deceased"). This causes the runtime system to throw an exception.

    From the above, we learn two key lessons: (1) self.somename is not the same as somename; (2) The cause of a crash can be far away from the site of the crash. Needless to say, number 2 is the more basic.
    Second, your example avoids using a nib file. Most examples I've found, as well as the documentation, counsel the use of Interface Builder - do you recommend avoiding it?

    No, not at all. Nibs are great, and I love IB. But all the things that make IB so easy to use in your dev environment, can make it horribly tedious to use in a forum example. When I stick to code, I can ask the OP to copy and paste directly from the forum (preferably into a clean Xcode template), and almost half the time this results in a working test bed at the other end.

    In this thread I wanted to demonstrate that a table view controller had no special ability to enable a navigation transition. But the only Xcode template that includes a nav controller (the Navigation-based App template) uses a table view controller! Basing the test bed on that template would've totally defeated the purpose of the example. If we were sitting in front of the same screen, I could show you how to cut the table view controller out of the nib and replace it with a vanilla controller at light speed. But in the forum, making all the controllers in code seemed by far the better choice.

    Btw, your kind comment about my work is deeply appreciated. I don't remember when anyone described my writing as lucid! Does it get much better than that in a technical forum?! Whether it's true or not seems beside the point. Your choice of words really made my day. Thank you.

    - Ray

    p.s.: Here's how to close your thread if and when you feel it's resolved: [http://discussions.apple.com/help.jspa#answers]
    iMac, Mac OS X (10.5.8)

Actions

More Like This

  • Retrieving data ...

Bookmarked By (0)

Legend

  • This solved my question - 10 points
  • This helped me - 5 points
This site contains user submitted content, comments and opinions and is for informational purposes only. Apple disclaims any and all liability for the acts, omissions and conduct of any third parties in connection with or related to your use of the site. All postings and use of the content on this site are subject to the Apple Support Communities Terms of Use.