Monday, April 27, 2009

iPhone tutorial: Navigation Controller from Scratch

In this post, we're going to test if we really understood what we learned in the previous parts. Those parts focused on analyses, but this time we're going to focus on synthesing, that is creating something of instead of just understanding. We're going to create a navigation controller which handles three custom made view controllers and provide the means to navigate between them. The title says we're going to start from scratch, but we're actually going to start from XCode's "Window-Based Application" template ,which is pretty close to "from scratch".

Start up XCode, choose "File/New Project" from the menu, select "Window-Based Application" and name it "WinNav". After XCode has done it's magic, we have the following project structure.

WinNav
  Classes
    WinNavAppDelegate.[hm]
  Resources
    MainWindow.xib
    Info.plist

When we're using Interface Builder (IB) to create objects for us, we should usually start by defining Objective C pointers in the source code and then use IB to "connect" the objects to the pointers. When the application is launched, the IB file loading procedure will actually assign values to the pointers.

Classes/WinNavAppDelegate.h

As was stated initially, we're going use a navigation controller to navigate between three custom view controllers. Furthermore, we're going to add "next"-buttons on the two first view controllers which will take us to the next view controller. Therefore, we start by declaring a navigation controller, three view controllers and two buttons - pretty logical, huh? How do we do this? Simple, open WinNavAppDelegate.h in XCode and add the following lines after the "UIWindow *window" line that came with the template:

// DON'T ADD THE FOLLOWING LINE - JUST USED FOR REFERENCE!!!
UIWindow *window;

// navigation controller
UINavigationController *navigationController;

// view controllers
UIViewController *viewController;
UIViewController *viewController2;
UIViewController *viewController3;

// buttons
UIButton *button;
UIButton *button2;


Since we're going to manipulate all these objects from IB, we have to declare them as properties (to make them accessible to external classes) as well as declare them as IBOutlets so that IB will detect them, when it scans our class defintion files. We do this by adding the following block of code after the 'window' property that came with the template.

// DON'T ADD THE FOLLOWING LINE - JUST USED FOR REFERENCE!!!
@property (nonatomic, retain) IBOutlet UIWindow *window;

// navigation controller
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;

// view controllers
@property (nonatomic, retain) IBOutlet UIViewController *viewController;
@property (nonatomic, retain) IBOutlet UIViewController *viewController2;
@property (nonatomic, retain) IBOutlet UIViewController *viewController3;

// buttons
@property (nonatomic, retain) IBOutlet UIButton *button;
@property (nonatomic, retain) IBOutlet UIButton *button2;
- (IBAction)next;
- (IBAction)next2;


If you've read the previous posts all this should be pretty straight forward to you, but you might have forgotten what the strange IBAction stuff is all about. An action is a way to associate an UI-event (touching a button for example) with an action (a method in a class).

Classes/WinNavAppDelegate.m

We declared a bunch of properties in the h-file, so the first thing we should do is to add matching @synthesize-instructions to actually create the code required to implement the property specifications.

// DON'T ADD THE FOLLOWING LINE - JUST USED FOR REFERENCE!!!
@synthesize window;

// navigation controller
@synthesize navigationController;

// view controllers
@synthesize viewController;
@synthesize viewController2;
@synthesize viewController3;

// buttons
@synthesize button;
@synthesize button2;


Ok, now that the properties are implemented, we should implement the action methods we declared using the IBAction statements in the h-file. Add the following lines to the end of the m-file, just above the @end-statement.

- (IBAction)next {
[navigationController pushViewController:viewController2 animated:YES];
}

- (IBAction)next2 {
[navigationController pushViewController:viewController3 animated:YES];
}


The 'next' method will be associate with the "next"-button on the first view controller and the "next2"-button will be associated with the "next"-button on the second view controller. Both these methods do the same thing, they "push" a view controller onto the navigation controller, which automatically (thanks to the navigation controller) will take us to the new view (the one we push). The button on the first view takes us to the second view and the button on the second view takes us to the third. Moving "backwards" is automatically handled by the navigation controller thanks to an automatically added "back"-button in the top left corner.

To make all the stuff we have declared visible once we start the application, we need to add the navigation controller's view to our window. We do this by adding a 'addSubView' method call in the 'applicationDidFinishLaunching' that came with the template:

// navigation controller
[window addSubview:[navigationController view]];

// DON'T ADD THE FOLLOWING LINE - JUST USED FOR REFERENCE!!!
[window makeKeyAndVisible];


Resources/MainWindow.xib

Now that we've prepared our source code for what we want to do, it's time to make an IB file which creates all the objects for us, so double-click on Resources/MainWindow.xib to load the file into IB. Switch to hierarchical view mode in the MainWindow.xib window in IB, by pressing the middle icon just above the "View mode" text in the upper left corner of the window. This should present the contents of the file as:

File's Owner [UIApplication]
First Responder [UIResponder]
Win Nav App Delegate [WinNavAppDelegate]
Window [UIWindow]

To really see if we understand how to use IB, we'll start by deleting the last two objects by selecting them and press backspace.

Win Nav App Delegate

So how do we re-create the "Win Nav App Delegate" object? Choose Tools/Library from the IB menu or press CMD-L. Select "Cocoa Touch Plugin" from the Library window and then "Controllers". Scroll down to the Object-icon and drag it into the MainWindow.xib window. It should be automatically selected so press CMD-4 to bring up the Identity tab of the Inspector window. Change the class to WinNavAppDelegate (our class) and you should see how the list of outlets get populated in the "Class outlets" section; all our buttons and view controllers as well as the window and navigation controller should be present. You should also see our two actions 'next' and 'next2' in the "Class actions" section.

Connect the "Win Nav App Delegate" to the 'delegate' property of "File's Owner" by selecting "File's owner" and CTRL-dragging from it to "Win Nav App Delegate". A blue line should appear and when you release the mouse button a window called "Outlets" should appear. Select 'delegate' from it. CTRL-click "Win Nav App Delegate" and then "File's Owner" to verify that the 'delegate' outlet is correctly connected. 

Window

Let's re-create the "Window" object, by going to the "Windows, Views & Bars" branch in the Library window. Drag the Window icon into the MainWindow.xib window. Connect the Window to the 'window' property of "Win Nav App Delegate" by selecting "Win Nav App Delegate" and CTRL-dragging from it to "Window".

Navigation Controller

What's next? Well, we need a navigation controller so select the "Controllers" branch in the Library window again. Drag the "Navigation Controller" into the MainWindow.xib window. See the small arrow to the left of the newly added object? Press it to expand the hierarchy under the controller and you should see a "Navigation Bar" object and a "View Controller (Navigation Item)" object. Yep, there's another small arrow to the left of this object, so press that as well. Look, a "Navigation Item (Navigation Item)" object appeared. IB apparently did a lot of work for us when we drag and dropped the "Navigation Controller" icon. Even if this is a "from scratch" tutorial we're going to accept this help since we'll be adding two more view controllers by ourselves soon. Connect this to the 'navigationController' outlet of "Win Nav App Delegate" by CTRL-dragging from "Win Nav App Delegate" to "Navigation Controller" and select the 'navigationController' outlet.

Navigation Bar

Automatically created when we dragged the "Navigation Controller" icon from the Library Window. This is used "internally" by the "Navigation Controller" and shouldn't be manipulated.

View Controller (Navigation Item)

Automatically created when we added the "Navigation Controller from the Library. It's a simple UIViewController, but CTRL-click it to see that it's 'navigationItem' property is connected to the "Navigation Item (Navigation Item)" object. This object was placed "under" the "Navigation Bar" object thanks to some special handling in IB - navigation controllers are prepared for it.

Navigation Item (Navigation Item)

Also automatically created by IB. This object was placed "under" the "View Controller" also thanks to some special handling in IB. It is used to display the title and navigation buttons of the "View Controller". Double-click it to bring the "edit window" for it to the front. Double-click the text "Navigation Item" at the top and change it to "First". Notice how the text "(First)" replaced ("Navigation Item)" in the MainWindow.xib window, both for the "Navigation Item" and the "View Controller".

A first test run

Let's take a brief pause and save our IB file (CMD-S), return to XCode to build and run (CMD-Return) and wait for the iPhone simulator to start. You should see a white background with a blue bar at the top saying "First". That's the title of the view controller that was automatically added by IB.

View Controller (Second)

To create this object, bring up the Library window in IB (CMD-L) and drag a "View Controller" icon from the "Controllers" section to the end of the list of objects in the MainWindow.xib window and drop it there. We should create a similar hierarchy as for the automatically created view controller so drag the "Navigation Item" icon from the "Windows, Views & Bars" secion in the Library and drop it onto the newly added view controller. It should "swallow" it so that it appears "under" it instead of just "below" it. Double-click the newly added navigation item to bring up the edit window for it and then double-click the "Title" text and change it to "Second".

Connect the newly created view controller object, by CTRL-dragging from the "Win Nav App Delegate" object to the "View Controller (Second)" object and choose the 'viewController2' outlet from the menu that pops up.

View Controller (Third)

Repeat the steps from above, but name it "Third" instead. Easy, huh?

Adding views

View controllers without views are pretty boring, so we'll add a view to each of our controllers. There are two ways of doing this. Either you drag the "View" icon from the "Windows, Views & Bars" section of the Library onto the appropriate "View Controller" object in the MainWindow.xib window or you double-click the appropriate "View Controller" object in the MainWindow.xib window to bring up the edit window and then drag the "View" icon into the edit window (the big area that says "View). Go ahead and try both alternatives.

Adding buttons

The navigation controller automatically provides a means for moving "backwards" among its view controllers, just like a "Back" button in a web browser, but it does not provide a "Forward button". Therefore, we have to provide our own. We need one button on the first view controller so we can get to the second and one on the second so we can get to the third.

Double-click on the first view controller (the one IB automatically added for us under the navigation controller) to bring up its edit window. As an alternative, you could also double-click on the view controllers view that we added above. Now, drag the "Round Rect Button" icon from the "Inputs & Values" section of the Library window (CMD-L) and drop the icon anywhere in the view controllers edit window that we just brought up. Don't drop it on the blue navigation bar at the top, though. Double-click the newly added button and name it "Second".

Connect the newly created button by CTRL-dragging from "Win Nav App Delegate" to the button and select the 'button' outlet from the menu that pops up.

Repeat the steps for the second view controller but name the button "Third" and connect it to the 'button2' outlet instead.

Time for a test run again

Save the IB-file (CMD-S) and go to XCode to build and run (CMD-R). You should see a white screen with the title "First" in the blue navigation bar at the top and a button titled "Second". That's pretty cool, but hey nothing happens when we press the button! What kind of tutorial is this? 

Bringing the buttons to life

Expand the hierarchies for the first and second "View" objects in the MainWindow.xib window by pressing the small arrow that appeared to the left of them when we added the buttons to them. You should see that a "Rounded Rect Button (Second)" and "Rounded Rect Button (Third)" have been added to the object hierarchy.

Select the first button in the MainWindow.xib window in IB and CTRL-drag to "Win Nav App Delegate" button. A small window named "Events" should pop up displaying the two IBActions 'next' and 'next2' we defined in Classes/WinNavAppDelegate.h. Choose 'next'.

Repeat the steps for the second button, but choose 'next2' from the list of events instead.

Time for another test run!

Save the IB-file (CMD-S) and go to XCode to build and run (CMD-R). Try to press the "Second"-button to see what happens! Whoa! That's amazing! Look at those smooth movements! Now try pressing the "Third"-button and see how we switch to the third view controller - pretty amazing, right? But that's not all, try pressing the "Second"-button in the upper left corner of the screen - inside the navigation bar. Yeah, that's a "Back"-button which takes us to the previous view controller. So now you can navigate freely between the three view controllers. Not bad, huh? ;)


31 comments:

  1. Thanks so much! Really thanks for this great and detail tutorial! The best Navigation Controller tutorial in my opinion

    ReplyDelete
  2. As a beginning iPhone programmer I have to say that this was an easy to understand tutorial with good practical information. Maybe go a little into popToViewController to make the app really navigatable.

    David House, San Francisco, CA
    davidjhouse@gmail.com

    ReplyDelete
  3. @Unreality and @dhouse: Glad to hear that you found the tutorial useful since I have tried to write in a somewhat different style than many other tutorial writers. I'm trying to write tutorials that I would have appreciated myself and since I am a bit slow I like to have every step and every detail explained ;)

    ReplyDelete
  4. A great tutorial, thank you very much !!
    Amir, Israel.

    ReplyDelete
  5. Great job! Nice and clear. Thanks for sharing!

    ReplyDelete
  6. Sorry for the dumb question, but how would we populate the table on the third view? There are plenty of examples out there on how to do this but none from the delegate and when a UINavigation Controller has already been setup. Any help would be greatly appreciated. Cheers.

    ReplyDelete
  7. @Dan: I don't follow you here, since there is no table on the third view - just a button. Maybe are you thinking of another of the tutorials I wrote?

    Regarding your question, I guess you mean how to alter the contents after the table has been set-up, for example if there is a way to "change the data set" - maybe through a button. I usually use the 'reloadData' method in UITableView for this. This will basically force 'cellForRowAtIndexPath' to be called for all the visible cells, which gives you a chance to update the contents.

    When you "change the data set" - by pressing a button? - you of course have to record somewhere that the set was changed so that 'cellForRowAtIndexPath' knows which set to display.

    As you can see in the API docs of 'reloadData' you can change every aspect of the table, so the reloaded table can have a different number of rows, sections, etc.

    Hope this was more or less what you asked about!

    ReplyDelete
  8. Thanks for getting back to me so quickly. My apologies about the table comment. You did answer another question indirectly with your solution. My app is to have the new Facebook App grid-like interface. Your tutorial has enabled me to do so. What I need is "Third View" to be a table. My questions is how? Do I create UITableViewController subclass and then somehow link it the viewController?

    ReplyDelete
  9. @Dan: When you're working with the standard iPhone UI elements like the NavigationController, you have to use ViewControllers since that is what those elements expect. Thus, if you only need a table on your view, go ahead and use the UITableViewController instead of a plain ViewController. It also adds some extra functionality, as you can see in the API docs for the class. You can also manage a table view by yourself, by creating an ordinary (subclass of) UIViewController and just add a table view to it.

    The easiset way to "make the third view into a table" in this example is to simply drag a Table View object from the Data Views section in IB and drop it in/on the View object of the third view controller. Adding contents to the table is outside the scope of this comment, but if you browse around in my other tutorials here, I'm sure you'll find a way to accomplish that as well :)

    ReplyDelete
  10. Henrikb,

    Thank you again for all of your help. I develop in many different languages but for some reason, conceptually, I am having a great deal of difficulty with the iPhone. I can't thank you enough for these types of tutorials and of course your responses. I have been reading around that it is not good practice to you have more than one view controller per xib. Is this true? I believe they are saying because then the root xib has a lot of content to load as opposed to several xibs where you can lazy load.

    ReplyDelete
  11. @Dan: I fully understand what you mean, the iPhone, Objective-C, Cocoa combination takes quite a while to fully grasp. I still haven't done it.

    Regarding the "one view controller per xib" recommendation, I think it is more of "one view per xib". A view controller per se does not contain that much data since it mostly contains "state and code" compared to a view which contains a lot of graphical components which can take up a lot of memory.

    That's how I usually do it, store each view in a xib and set the corresponding view controller as the "File's owner" in the view xib in IB.

    ReplyDelete
  12. Thank you a lot for this one henrik.....the ones from scratch are so much better than the ones where you explain stuff.....

    ReplyDelete
  13. Great tutorial! The NavigationController outlet for "WinNavAppDelegate" wasn't showing up in Interface Builder while I was following the tutorial. I had to close Interface Builder, choose "Build and Run" in Xcode, then reopen Interface Builder, and like magic all the missing outlets showed up! Hope it helps anyone else that comes across this. Xcode 3.2.1 on OS X 10.6.2.

    ReplyDelete
  14. @Josh: Yes, sometimes you don't see everything you expected in IB and I probably should have added a hint about saving the source files in XCode before switching to Interface Builder. However, there is a less drastical solution to the problem than the one you discovered. The important thing is to save all the source files in XCode. "Build and run" is an effecient way of accomplishing this, and I use it myself. The next step - updating Interface Builder with the changes performed in XCode - can however be accomplished by selecting "Reload all Class Files" in the "File" menu of IB.

    @spark and Josh: Nice that you liked the tutorial and point me in the direction to go, that is, writing "from Scratch" tutorials. I wish I had more time for this stuff, but currently my workload is quite heavy. I would have loved to start looking at the iPad and do some "from scratches" for that one :)

    ReplyDelete
  15. Nice tutorial. I have a question. I have a similar app . In the 2nd VC I have some buttons. When the correct button is selected, the 3rd VC is shown using PresentModalView and dismissed after 3-4sec. The buttons on 2nd VC get updated and when the correct button is pressed 3rd VC is shown again. This goes on. Now the issue I have is which method should I use in 3rd VC to update the imageView? If I use viewdidload it gets called only once. I am not able to fire viewdidappear or viewwillappear.

    ReplyDelete
  16. About the viewcontroller, "CTRL-click it to see that it's 'navigationItem' property is connected to the "Navigation Item (Navigation Item)" object." I didn't see any navigation item at that point. Can you advise?

    ReplyDelete
  17. @jonathan.nyc: Thanks for pointing this out! It turned out to be a InterfaceBuilder version issue. When I wrote this post I was using SDK 2.2, but now when I reran my instructions on SDK 3.1 I see that things have been changed. One of the things that have been changed is that the 'navigationItem' property no longer shows up in IB.

    The step you referred to was not necessary to proceed, but rather just a confirmation so you can skip it and go ahead. The rest should hopefully be relevant on a current SDK.

    ReplyDelete
  18. Hi,

    I'm a beginner and went through with your tutorial. I did everything. But the problem is when I press "Second" button view doesn't change?

    ReplyDelete
  19. Hi, thanks for the tutorial!
    Which is the role of the first UIViewController declared 'viewController'?

    ReplyDelete
  20. Very detail tutorial. Good job. Maybe if you attach some graphical reference, it will be more helpful. I wonder how i can set the application (first view, second view n etc) to landscape orientation. I already changed the interface orientation to landscape in the info.plist. I also added the script below to delegate.m:
    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
    }

    But it seems the outcome not quite right. It did change to landscape but not for the view.

    Can you advise?

    ReplyDelete
  21. Thank you very much for the Tutorial. Great Job. Helped me a lot to understand how the hierarchy works.

    ReplyDelete
  22. I've tried about 4 tutorials to try and make this same thing happen and they all either run with errors after following word by word, or they don't explain word by word and cant get past a Very early step.. this is the first tutorial that explains EVERYTHING, and well too..

    thanks so much for helping out an extreme newbie to XCode and IB.. and for making it make sense.. much appreciated..

    ReplyDelete
  23. Great tutorial. A beginner question...

    I noticed you're not doing anything with memory management. Is this because these are convenience methods? Just a bit confused here.

    Thanks again.

    ReplyDelete
  24. @Tom: Great that you found my tutorial useful, and your confusion about the lack of memory management is understandable.

    When I started to write my tutorials I was still learning a lot and was only beginning to understand the memory management issues in the iPhone. Especially the details about how to manage objects created by the XIB file (Interface Builder) were very sketchy to me.

    Because of this and to keep the tutorials as short and focused as possible I chose to leave out memory management completely. Not the best decision, but back then I was focusing on learning the API and IB to decide how I should develop my game - with or without IB :)

    I tried to make up for this by publishing a few posts focused entirely on memory management. You can find them if you click on the "memory management" label in the right sidebar.

    ReplyDelete
  25. This tutorial is much more useful than Apple's NavigationController sample (which adds a tableviewcontroller for some strange reason).

    I have 2 published iPhone apps, both based on manual viewcontroller manipulation, but my next project requires a Navigation Controller interface, and I am using your method to implement it.

    Thanks again,

    Owen

    ReplyDelete
  26. Nice tutorial and got this working but the only problem is that this navigation controller is needed as a tab in an existing tab bar. The only problem is how do I gain access to the main application window to add the navigation controller to be seen. If I add another window to this class it works but then my tab bar disappears.

    If I don't add the window then the navigation controller does not show up although the view does.

    Please advise. Thanks

    ReplyDelete
  27. Thanks a lot, great tutorial to learn from! :)

    ReplyDelete
  28. Hi,
    I have seen ur blog its helpful for me but i need to start my navigation based app from third page of app.Do u have any tutorial.
    I need to run the tutorial in below link from 3rd page of application in first and second page i need buttons and from third page i have to start the app in the below link.[in IPAD]
    give me reply its urgent.I havent found tihs kind of tutorial in any blog.
    mohammedrafiq82@gmail.com

    http://www.iphonesdkarticles.com/2008/11/sqlite-tutorial-updating-data.html

    thank u

    ReplyDelete
  29. Great tutorial, although i think things have been updated a bit since authoring.
    Been struggling to get into objective c despite coming from csharp backgrounds.

    Thank!

    ReplyDelete
  30. Hi, Its a great tutorial Thanks for reducing the pain in my head for the project I have.

    Anyhow, I m supposed to introduce Back in the navigation for my app, and it displays the title "First" . And do tell how to lock back navigation. like a form submission if u press submit then "NO RETURN" in Navigation controller. I am quite new to iOS development, will appreciate ur help.

    Contact me on aleem.ullah@yahoo.com or just leave a reply here.

    Regards

    Aleem ullah

    ReplyDelete
  31. Please provide completed source code at end of tutorial.

    ReplyDelete