Programming4us
         
 
 
Programming

iPhone Programming : Other View Controllers - Modal View Controllers

7/16/2011 9:25:44 AM
You’ll have seen a modal controller in action many times when using your iPhone. A view slides in from the bottom of the screen and is usually dismissed with a Done button at the top of the screen. When dismissed, it slides back down the screen, disappearing at the bottom.

In the main controller we would generally have a button or other UI element; tapping this would trigger an event linked to the following method in the view controller, which would bring up the modal view:

-(void)openNewController:(id)sender {
OtherController *other = [[OtherController alloc] init];
[self presentModalViewController:other animated:YES];
[other release];
}

In the modal view itself, we would implement a button or some other way to close the view, which would call this method in the view controller:

-(void)doneWithController:(id)sender {
[self dismissModalViewControllerAnimated:YES];
}

This dismisses the current modal view.

1. Modifying the City Guide Application

The best way to explain the modal view is to show it in action. For that we’re going to go back to the City Guide application we built in  this article. We’re going to make some fairly extensive changes to it, so you should make a copy of the project first and work with the copy while you make your modifications. In this section, I’ll show you how to take your code apart and put it back together again in an organized fashion. This occurs a lot when writing applications, especially for clients who have a tendency to change their mind about what they want out of the application in the first place.

Open the Finder and navigate to the location where you saved the CityGuide project; see Figure 1.

Figure 1. The CityGuide project folder in the Finder


Right-click or Ctrl-click on the folder containing the project files and select Duplicate. A folder called CityGuide copy will be created containing a duplicate of our project. You should probably rename it to something more sensible. I suggest CityGuide2. Now open the new version of the project in Xcode and select ProjectRename from the Xcode menu bar. Enter CityGuide2 when prompted and click on the Rename button to rename the project.

In this article, we built an application that lets users both add and delete city entries in our table view. Adding the functionality to delete table view cells was fairly simple; the complicated part was adding the ability to add cities. So, let’s take a step back and look at another way to implement that functionality.

First we’re going to go into the RootController implementation and back out of the changes that allowed users to edit the table view. We’re going to replace the Edit button and the associated implementation with an Add button, reusing the AddCityController code and associated view, but presenting the Add City view modally instead of using the navigation controller.

You may wonder about deleting lots of perfectly good code, but refactoring functionality like this is a fairly common task when you change your mind about how you want to present information to the user, or if the requirements driving the project change. This is good practice for you.


Note:

If you want to do a global find (and replace) over the entire project for a word or phrase you can do so from the Edit menu. Selecting EditFindFind in Project will bring up the Project Find window.


To remove functionality like this, first you need to figure out what needs to be removed. If you don’t know the author of the original application this can sometimes be difficult. Do a project-wide search for “editing”, as shown in Figure 2. If you do that you’ll see that the only mention of “editing” is in the RootController.m file. The changes we’ll need to make are actually fairly tightly constrained inside a single class. We’ll have to make some minor changes elsewhere in the project. Limiting the scope of necessary changes when refactoring code in this way is one of the main benefits of writing code in an object-oriented manner.

Figure 2. The results of a global find across the CityGuide2 project for “editing”


Open the RootController.m file in Xcode. Begin the refactoring by deleting the following methods in their entirety:

  • setEditing:animated:

  • tableView:commitEditingStyle:forRowAtIndexPath:

  • tableView:editingStyleForRowAtIndexPath:


Note:

Remember that the methods as they appear in the file have longer, more complicated names. For example, setEditing:animated: is (void)setE⁠diting:(BOOL)editing animated:(BOOL) animated.


Next, do the following:

  1. In the viewDidLoad: method, remove the line that adds the self.editButtonItem to the navigation bar.

  2. In the tableView:cellForRowAtIndexPath: method, remove the section enclosed in the if( self.editing ) { ... } conditional statement, and the else { ... } statement that adds the “Add New City...” cell. Additionally, you should remove the line that sets the editingAccessoryType inside the conditional statement.

  3. Similarly, remove the if( self.editing ) { ... } conditional statement in the tableView:numberOfRowsInSection: method.

  4. Finally, in the tableView:didSelectRowAtIndexPath: method remove the && !⁠self.editing expression from the first if block. Remove the second if block (which deals with what happens if we are editing) in its entirety.

We’re done. If you do a global find in the project for “editing” you should now come up blank, and the class should appear as shown here:

#import "RootController.h"
#import "CityGuideDelegate.h"
#import "City.h"
#import "CityController.h"
#import "AddCityController.h"

@implementation RootController

@synthesize tableView;

#pragma mark UIViewController Methods

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

- (void)viewDidLoad {
self.title = @"City Guide";
CityGuideDelegate *delegate =
(CityGuideDelegate *)[[UIApplication sharedApplication] delegate];
cities = delegate.cities;
}

- (void)dealloc {
[tableView release];
[cities release];
[super dealloc];
}

#pragma mark UITableViewDataSource Methods

- (UITableViewCell *)tableView:(UITableView *)tv
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =
[tv dequeueReusableCellWithIdentifier:@"cell"];
if( nil == cell ) {
cell = [[[UITableViewCell alloc]
initWithFrame:CGRectZero reuseIdentifier:@"cell"] autorelease];
}

if (indexPath.row < cities.count ) {
City *thisCity = [cities objectAtIndex:indexPath.row];
cell.textLabel.text = thisCity.cityName;
cell.textLabel.textColor = [UIColor blackColor];
}
return cell;
}

- (NSInteger)tableView:(UITableView *)tv
numberOfRowsInSection:(NSInteger)section
{
NSInteger count = cities.count;
return count;
}

#pragma mark UITableViewDelegate Methods

- (void)tableView:(UITableView *)tv
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
CityGuideDelegate *delegate =
(CityGuideDelegate *)[[UIApplication sharedApplication] delegate];

if (indexPath.row < cities.count ) {
CityController *city =
[[CityController alloc] initWithIndexPath:indexPath];
[delegate.navController pushViewController:city animated:YES];
[city release];
}
[tv deselectRowAtIndexPath:indexPath animated:YES];
}

@end


Since you’ve now made fairly extensive changes to the view controller, you should test it to see if things are still working. Click the Build and Run button on the Xcode toolbar, and if all is well you should see something very similar to Figure 3. Tapping on one of the city names should take you to its city page as before.

Figure 3. The stripped-down City Guide application, looking a lot like it did in Figure 3 in this article


We’ve deleted a lot of code, so let’s write some more. In the viewDidLoad: method we need to replace the Edit button that we deleted with an Add button.

Let’s add a button of style UIBarButtonSystemItemAdd and set things up so that when it is clicked it will call the addCity: method in this class. Add the following code to the viewDidLoad: method:

self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self
action:@selector(addCity:)];

Since there isn’t an addCity: method right now, we need to declare it in the RootController.h interface file. Open that file, and add this line after the @interface { ...}@end directive: declaration but before the

- (void)addCity:(id)sender;

Now add the implementation to the RootController.m file:

- (void)addCity:(id)sender {
AddCityController *addCity = [[AddCityController alloc] init];
[self presentModalViewController:addCity animated:YES];
[addCity release];
}

This looks almost identical to the snippet of code I showed you at the beginning of this section, but the modal view we’re going to display is the one managed by our AddCityController class.

Now we need to make a couple of small changes to our AddCityController class. Open the AddCityController.h interface file in Xcode and declare the saveCity:IBAction. Add this line after the @interface { ... } statement but before the @end directive: method as an

- (IBAction)saveCity:(id)sender;

Open the implementation file (AddCityController.m), and remove the last line (where we pop the view controller off the navigation controller) and replace it with a line dismissing the modal view controller. You’ll also change the return value of the saveCity: method from void to IBAction here just as you did in the interface file:

- (IBAction)saveCity:(id)sender {
CityGuideDelegate *delegate =
(CityGuideDelegate *)[[UIApplication sharedApplication] delegate];
NSMutableArray *cities = delegate.cities;

UITextField *nameEntry = (UITextField *)[nameCell viewWithTag:777];
UITextView *descriptionEntry =
(UITextView *)[descriptionCell viewWithTag:777];

if ( nameEntry.text.length > 0 ) {
City *newCity = [[City alloc] init];
newCity.cityName = nameEntry.text;
newCity.cityDescription = descriptionEntry.text;
[cities addObject:newCity];

RootController *viewController = delegate.viewController;
[viewController.tableView reloadData];
}
[self dismissModalViewControllerAnimated:YES];

}

We’re pretty much there at this point; however, before we finish with our changes here we also need to go up to the viewDidLoad: method and delete the lines where we add the Save button to the view (it’s a single statement beginning with self.navigationI⁠tem.rightBarButtonI⁠tem that spans multiple lines).

Make sure you save the changes you made to the AddCityController class, and open the AddCityController.xib file inside Interface Builder.

First, drag and drop into the view a navigation bar (UINavigationBar) from the Library window (select Cocoa TouchWindows, Views & Bars). Position it at the top of the view, and resize the table view so that it fits in the remaining space. While you’re there, change the title of the navigation bar from “title” to “Add New City”.

Next, drag and drop a bar button item (UIBarButtonItem) onto the navigation bar and position it to the left of the title. In the Attributes Inspector (⌘-1) change the Identifier from Custom to Done. You’ll see that this changes both the text and the style of the button.

Finally, click on File’s Owner in the AddCityController.xib window and switch to the Connections Inspector (⌘-2). Connect the saveCity: received action to the Done button, as I’ve done in Figure 6-15. Save your changes to the NIB file, as we’ve now finished refactoring our City Guide application.

Click Build and Run on the Xcode toolbar to compile and start the application in iPhone Simulator. When the application starts you should see something like Figure 6-16. Clicking the Add button in the navigation bar should bring up our “Add City” view; when it does, enter some information and click Done. You should see your test city appear in the main table view.

Well done. We’ve just taken the City Guide application apart, put it back together again, and made it work slightly differently. But what if you disliked the way we implemented the ability to add cities in the first version of the application, preferring this approach, but you still want to retain the ability to delete cities? You could still implement things so that a left-to-right swipe brought up the Delete button for the row; for instance, Apple’s Mail application that ships with the iPhone and iPod touch takes this approach. Just adding the following method back into RootController.m will reimplement this functionality:

- (void) tableView:(UITableView *)tv
commitEditingStyle:(UITableViewCellEditingStyle) editing
forRowAtIndexPath:(NSIndexPath *)indexPath {
if( editing == UITableViewCellEditingStyleDelete ) {
[cities removeObjectAtIndex:indexPath.row];
[tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationLeft];
}
}

Figure 4. Connecting the SaveCity: received action to the Done button in our newly modified AddCityController.xib file


Figure 5. The newly rewritten City Guide application, with our Add button on the right of the navigation bar

Other -----------------
- jQuery 1.3 : DOM Manipulation - Inserting new elements
- jQuery 1.3 : DOM Manipulation - Manipulating attributes
- DirectX 10 Game Programming : Adding the DirectX Libraries
- jQuery 1.3 : AJAX - Additional options
- jQuery 1.3 : AJAX and events & Security limitations
- jQuery 1.3 : AJAX - Keeping an eye on the request
- jQuery 1.3 : AJAX - Passing data to the server
- iPhone Programming : Other View Controllers - Tab Bar Applications
- iPhone Programming : Other View Controllers - Utility Applications
- iPhone Programming : Table-View-Based Applications - Adding a City View
- iPhone Programming : Table-View-Based Applications - Adding Navigation Controls to the Application
- iPad SDK : Popovers - Popover Preparations
- iPad SDK : Preparing Dudel for a New Tool (part 5) - Rendering Multiple Styles
- iPad SDK : Preparing Dudel for a New Tool (part 4) - Creating a New Drawable Class
- jQuery 1.3 : AJAX - Loading data on demand (part 3) - Loading an XML document
- jQuery 1.3 : AJAX - Loading data on demand (part 2) - Working with JavaScript objects
- jQuery 1.3 : AJAX - Loading data on demand (part 1) - Appending HTML
- Coding JavaScript for Mobile Browsers (part 13) - Zoom and rotate gestures
- Coding JavaScript for Mobile Browsers (part 12) - Swipe gesture
- Coding JavaScript for Mobile Browsers (part 11)
 
 
Most View
- Keyword Research Tools (part 4)
- Application Lifecycle Management with BizTalk Server 2009
- SQL Server 2008 : Indexing for Performance - Putting It All Together (part 1)
- BizTalk Server 2009 : Identifying Standard Message Exchange Patterns (part 3)
- Programming with SQL Azure : WCF Data Services (part 1)
- SharePoint 2010 : Get Started with Social Features
- Implementing Windows Vista’s Internet Security and Privacy Features (part 4) - Surfing the Web Securely - Thwarting Phishers with the Phishing Filter
- Windows Phone 7 : Finding Text on a Web Page
- Windows Server 2008 : Installing the Web Server Role (part 1)
- Exchange Server 2010 : Managing Web and Mobile Access (part 1) - Enabling and Disabling Outlook Web App Features, Configuring Ports, IP Addresses, and Host Names Used by Web Sites
Top 10
- Implementing Edge Services for an Exchange Server 2007 Environment : Utilizing the Basic Sender and Recipient Connection Filters (part 3) - Configuring Recipient Filtering
- Implementing Edge Services for an Exchange Server 2007 Environment : Utilizing the Basic Sender and Recipient Connection Filters (part 2)
- Implementing Edge Services for an Exchange Server 2007 Environment : Utilizing the Basic Sender and Recipient Connection Filters (part 1)
- Implementing Edge Services for an Exchange Server 2007 Environment : Installing and Configuring the Edge Transport Server Components
- What's New in SharePoint 2013 (part 7) - BCS
- What's New in SharePoint 2013 (part 6) - SEARCH
- What's New in SharePoint 2013 (part 6) - WEB CONTENT MANAGEMENT
- What's New in SharePoint 2013 (part 5) - ENTERPRISE CONTENT MANAGEMENT
- What's New in SharePoint 2013 (part 4) - WORKFLOWS
- What's New in SharePoint 2013 (part 3) - REMOTE EVENTS