Thursday, May 6, 2010

Creating a Navigation-Based Application

In this example I'll show you how to create an application that will:
  • Use a Navigation-Based interface.
  • Switch between different views using the Navigation-Based application.
  • Send messages to different parts of the application using the Notification Center.

Creating a new Navigation-Based Application.

Open Xcode and select New Project from the File menu.
Select Navigation-Based Application and click on Choose.

Name it NavigationApplication.


Creating the necessary files.

On the Xcode window Ctrl + Click the Classes folder and select Add > New File.
Choose UIViewController subclass from the Cocoa Touch Classes. Make sure the "With XIB for user interface" is checked. 

Name this class ViewOneController.


Create another class and name it ViewTwoController.

Move the .xib files to the Resources folder.


With this we have created two views and their respective controller classes.

Editing the different views from the Interface Builder.

Double click on ViewOneController.xib to open up the Interface Builder. Press Command + Shift + L to bring up the Library window. Add a Button and a Text View to the view. Edit your view to make it look like this (you can press Command + 1 to bring up the Attributes Inspector if needed):

Click on File's Owner and press Command + 4 to show the Identity Inspector window. 
On the Class Identity section of the window change the Class to ViewOneController.



Click on the arrow next to ViewOneController. Add an action named sendMessage: and an outlet named textView of type UITextView.





Press Command + S to save and then select Write Class Files from the File menu.
Click on Save then Merge. Choose left for both differences on ViewOneController.h and left for the first difference on ViewOneController.m

Click on File's Owner again and press Command + 2 to show the Connection Inspector.
Link textView with the Text View from the view.


Now link sendMessage: with the button from the view.


On the actions from the button select Touch Up Inside.

Link view with... the View...



Press Command + S to save and go back to Xcode.

Now open the ViewTwoController.xib. Add a Label to the view and write "Received Message:" in it. Add a Text View to the view and remove the text in it. Your view should look like this:


Click on File's Owner and press Command + 4 to bring up the Identity Inspector. Select ViewTwoController in Class.

Create a new outlet and name it receivedText and make it of type UITextView.



Press Command + S to save and click on File > Write Class Files. Click on save and then merge. Select the left file for ViewTwoController.h.

Click on File's Owner and press Command + 2.
Link receivedText with the Text View...



and also link view with the View.

Save again and exit the interface builder.


Adding the navigation code to the application.

Go back to Xcode and open RootViewController.h.
Make your code look like this:

#import <UIKit/UIKit.h>

@interface RootViewController : UITableViewController {
NSMutableArray *navigationArray;
}

@property (nonatomic,retain) NSMutableArray *navigationArray;

@end
We created an array called navigationArray. This array will be used to store information that is needed to navigate trough the views in our app.

Now open RootViewController.m and add the following imports:

#import "ViewOneController.h";
#import "ViewTwoController.h";

And add the following method:

//awakeFromNib is called when a xib or nib file is initialized
-(void) awakeFromNib{
//this array is used to add the views that we want to access from
//the navigation controller
//even though the menu items aren't added directly to the table in this method
navigationArray = [[NSMutableArray alloc] init];
//initialize the first view and add it to the table array
ViewOneController *viewOneController = [[ViewOneController alloc] init];
viewOneController.title = @"View One";
[navigationArray addObject:
[NSDictionary dictionaryWithObjectsAndKeys:
@"View One", @"title", viewOneController, @"controller", nil]];
[viewOneController release];
//initialize the second view and add it to the table array
ViewTwoController *viewTwoController = [[ViewTwoController alloc] init];
viewTwoController.title = @"View Two";
[navigationArray addObject:
[NSDictionary dictionaryWithObjectsAndKeys:
  @"View Two", @"title", viewTwoController, @"controller", nil]];
[viewTwoController release];
//The title is needed if we want to be able to navigate back
//to the menu from the other views
self.title = @"Main";
}

Search for the method tableView:numberOfRowsInSection: and change the code to this:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [navigationArray count];
}
With this method we change the number of rows in the navigation menu. Since the navigationArray only has 2 entries we could change this to " return 2; " but is better to leave it this way in case we want to add more views in the future.

On the method next to this one, tableView:cellForRowAtIndexPath: , add the following code below the //Set up the cell... comment.

    // Set up the cell...
[cell.textLabel setText:[[navigationArray objectAtIndex:indexPath.row] objectForKey:@"title"]];


This adds the title for each view to the navigation menu.

Now go to the method tableView:didSelectRowAtIndexPath: and edit the code to make it look like this:






- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    
UIViewController *anotherViewController = [[navigationArray objectAtIndex:indexPath.rowobjectForKey:@"controller"];
[self.navigationController pushViewController:anotherViewController animated:YES];
}

This adds the logic for the navigation.

Finally, go to the dealloc method and add this line:
[navigationArray release]; 
*It is necessary to add a "release" for the variables where we use "retain".

Testing the navigation interface.

You can try running the application now. Press Command + R to run the simulator.


Try navigating trough the app. The logic for sending a message isn't there yet, but you can try using the elements in the view anyway.




*If the keyboard blocks the button when you click/touch a Text View you can try setting your button a little higher. I'll explain a method to hide the keyboard at the end of this tutorial.

Adding the code to enable message sending.

Let's add the code for sending messages between the views.
Open NavigationApplicationAppDelegate.h and make your code look like the following.

#import <UIKit/UIKit.h>

@interface NavigationApplicationAppDelegate : NSObject {
    NSString *message;
    UIWindow *window;
    UINavigationController *navigationController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;

-(void)setMessage: (NSString *)newMessage;
-(NSString *)getMessage;

@end

The message variable is used to save the information that we want to share between the two views.

Add the following methods to NavigationApplicationAppDelegate.m.

- (void)setMessage:(NSString *)newMessage{
message = newMessage;
[[NSNotificationCenter defaultCenter] postNotificationName:@"messageSentEvent" object:self]; 
}


- (NSString *)getMessage{
[message retain];
return message;
}

 As you can see the setMessage: method sends a notification to the notification center. We will add an observer to View Two so it is notified when a different message is set.  The retain method is called by getMessage to help the object "retain" the value of this variable. If this method is not called the application may not function correctly. (You can try removing this line of code and sending a lot of messages when the application is finished if you want to see the effects of this.)

We need to make one last change to this file. Add the bold line to the dealloc method.

- (void)dealloc {
[message release];
[navigationController release];
[window release];
[super dealloc];
}
A release needs to be added because we used the retain method on message.

Now open ViewOneController.m. Add the following import:

#import "NavigationApplicationAppDelegate.h";

Add the following code to the sendMessage: method:

- (IBAction)sendMessage:(id)sender {
//gets the delegate for this application
    NavigationApplicationAppDelegate *navigationAppDelegate = 
(NavigationApplicationAppDelegate *)[[UIApplication sharedApplication] delegate];
//set the string value
NSString *newMessage = textView.text;
//call the setMEssage method from the delegate
[navigationAppDelegate setMessage:newMessage];
//hide the keyboard
[textView resignFirstResponder];
}
Open ViewTwoController.m and add the following import:

#import "NavigationApplicationAppDelegate.h"
Now add the following methods:

//this method is used so an observer can be added as soon as this object is initialized
-(id) init { 
self = [super init]; 
if (self != nil) { 
// add an observer
[[NSNotificationCenter defaultCenter] addObserver:self 
selector:@selector(messageReceived) name:@"messageSentEvent" object:nil]; 
}
return self;
}

//gets the message everytime a message is received
-(void) messageReceived{
NavigationApplicationAppDelegate *NavigationAppDelegate =
[[UIApplication sharedApplication] delegate];
[receivedText setText: [NavigationAppDelegate getMessage]];
}

//gets the message when the view is first loaded
-(void)viewWillAppear:(BOOL)animated { 
NavigationApplicationAppDelegate *NavigationAppDelegate =
[[UIApplication sharedApplication] delegate];
[receivedText setText: [NavigationAppDelegate getMessage]];

}

When the object is initialized an observer will be added. This observer specifies that every time a notification with the name messageSentEvent is received the method messageReceived will be executed.

Observers are used to subscribe to the notification center. After subscribing, the notification center will be in charge of notifying the subscribing objects when a new event occurs.

Testing the complete application.

Let's run the application. Press Command + R to compile and start the simulator.

Go to the first view and write a message. After writing the message click / touch on "Send Message".


Go back to the main menu and then to the second view.

As you can see the message was succesfully received by the second view.

Extra: Adding a hidden button to hide the keyboard.

The keyboard that appears when you click / touch over Text Views and Text Fields can be really annoying sometimes. I'll show you a method to hide the keyboard... even though...  It might not be a very orthodox way to do this.

Double click on ViewOneController.xib to open the Interface Builder.

Select all the elements from the view and press Command + x to cut them. Press Command + Shift + L to bring up the Library window. Now add a button to the view and expand it to fill the whole view.

Press Command + 1 to show the Attributes Inspector. Select Custom in Button Type.


By using Custom as a Type for the Button the button becomes invisible. Now click on the view title bar and press Ctrl + V to paste the elements you cutted before. Be sure to place them correctly over the view since they might appear a little messed up at first.

Now click on File's Owner and press Command + 2 to show the Connection Inspector.
Re-link the outlets to the elements on the view.

textView -> Text View on the View

sendMessage: -> Button and then Touch Up Inside


Press Command + 4 to go to the Identity Inspector window. Click on the arrow next to the Class name and create a new action named clickOnBackgroundButton: . Save and Write Class Files.

Select left for the only difference in ViewOneController.h, click on the red button and save the file.
Select left too for ViewOneController.m and save the file.

Select the Connection Inspector again and link the clickOnBackgroundButton: action to the background of the view (which this time isn't the view itself, but a hidden button).



When prompted select Touch Up Inside.


Save and exit Interface Builder.

Go back to Xcode and to ViewOneController.m.

Add the following code to the clickOnBackgroundButton: method:

- (IBAction)clickOnBackgroundButton:(id)sender {
    [textView resignFirstResponder];
}
Yup, that's the only line of code you need to add. If you were to have various Text Views or Text Fields in your view, you would need to call the resignFirstResponder method for each of those elements.

Run your application and click on the Text View from the first view. Now click the background. This time the keyboard should hide automatically without the need of pushing the Send Message button.

*Note: Remember the Text View is on top of the hidden button, if you press over it the keyboard won't hide.

And well... that's it for this example. I hope you learned how to create a Navigation-Based application and how to use the Notification Center.

No comments:

Post a Comment