Chapter 6

Multiview Applications

Up until this point, we've written applications with a single view controller. While there certainly is a lot you can do with a single view, the real power of the iOS platform emerges when you can switch out views based on user input. Multiview applications come in several different flavors, but the underlying mechanism is the same, regardless of how it may appear on the screen.

In this chapter, we're going to focus on the structure of multiview applications and the basics of swapping content views by building our own multiview application from scratch. We will write our own custom controller class that switches between two different content views, which will give you a strong foundation for taking advantage of the various multiview controllers that Apple provides.

But before we start building our application, let's see how multiple-view applications can be useful.

Common Types of Multiview Apps

Strictly speaking, we have worked with multiple views in our previous applications, since buttons, labels, and other controls are all subclasses of UIView and can all go into the view hierarchy. But when Apple uses the term “view” in documentation, it is generally referring to a UIView or one of its subclasses that has a corresponding view controller. These types of views are also sometimes referred to as content views, because they are the primary container for the content of your application.

The simplest example of a multiview application is a utility application. A utility application focuses primarily on a single view but offers a second view that can be used to configure the application or to provide more detail than the primary view. The Stocks application that ships with iPhone is a good example (see Figure 6–1). If you click the little i icon in the lower-right corner, the view flips over to let you configure the list of stocks tracked by the application.

Image

Figure 6–1. The Stocks application that ships with iPhone has two views: one to display the data and another to configure the stock list.

There are also several tab bar applications that ship with the iPhone, such as the Phone application (see Figure 6–2) and the Clock application. A tab bar application is a multiview application that displays a row of buttons, called the tab bar, at the bottom of the screen. Tapping one of the buttons causes a new view controller to become active and a new view to be shown. In the Phone application, for example, tapping Contacts shows a different view than the one shown when you tap Keypad.

Image

Figure 6–2. The Phone application is an example of a multiview application using a tab bar

Another common kind of multiview iPhone application is the navigation-based application, which features a navigation controller that uses a navigation bar to control a hierarchical series of views. The Settings application is a good example. In Settings, the first view you get is a series of rows, each row corresponding to a cluster of settings or a specific app. Touching one of those rows takes you to a new view where you can customize one particular set of settings. Some views present a list that allows you to dive even deeper. The navigation controller keeps track of how deep you go and gives you a control to let you make your way back out to the previous view.

For example, if you select the Sounds preference, you'll be presented a view with a list of sound-related options. At the top of that view is a navigation bar with a left arrow that takes you back to the previous view if you tap it. Within the sound options is a row labeled Ringtone. Tap Ringtone, and you're taken to a new view (see Figure 6–3) featuring a list of ring tones and a navigation bar that takes you back to the main Sounds preference view. A navigation-based application is useful when you want to present a hierarchy of views.

Image

Figure 6–3. The iPhone Settings application is an example of a multiview application using a navigation bar.

On the iPad, most navigation-based applications, such as Mail, are implemented using a split view, where the navigation elements appear on the left side of the screen, and the item you select to view or edit appears on the right. You'll learn more about split views and other iPad-specific GUI elements in Chapter 10.

Because views are themselves hierarchical in nature, it's even possible to combine different mechanisms for swapping views within a single application. For example, the iPhone's iPod application uses a tab bar to switch between different methods of organizing your music, and a navigation controller and its associated navigation bar to allow you to browse your music based on that selection. In Figure 6–4, the tab bar is at the bottom of the screen, and the navigation bar is at the top of the screen.

Image

Figure 6–4. The iPod application uses both a navigation bar and a tab bar.

Some applications make use of a toolbar, which is often confused with a tab bar. A tab bar is used for selecting one and only one option from among two or more. A toolbar can hold buttons and certain other controls, but those items are not mutually exclusive. A perfect example of a toolbar is at the bottom of the main Safari view (see Figure 6–5). If you compare the toolbar at the bottom of the Safari view with the tab bar at the bottom of the Phone or iPod application, you'll find the two pretty easy to tell apart. The tab bar is divided into clearly defined segments, while the toolbar, typically, is not.

Image

Figure 6–5. Mobile Safari features a toolbar at the bottom. The toolbar is like a free-form bar that allows you to include a variety of controls.

Each of these types of multiview application uses a specific controller class from the UIKit. Tab bar interfaces are implemented using the class UITabBarController, and navigation interfaces are implemented using UINavigationController.

The Architecture of a Multiview Application

The application we're going to build in this chapter, View Switcher, is fairly simple in appearance, but in terms of the code we're going to write, it's by far the most complex application we've yet tackled. View Switcher will consist of three different controllers, three nibs, and an application delegate.

When first launched, View Switcher will look like Figure 6–6, with a toolbar at the bottom containing a single button. The rest of the view will contain a blue background and a button yearning to be pressed.

Image

Figure 6–6. When we first launch the application, we'll see a blue view with a button and a toolbar with its own button.

When the Switch Views button is pressed, the background will turn yellow, and the button's title will change (see Figure 6–7).

Image

Figure 6–7. When we press the Switch Views button, the blue view flips over to reveal the yellow view.

If either the Press Me or Press Me, Too button is pressed, an alert will pop up indicating which view's button was pressed (see Figure 6–8).

Image

Figure 6–8. When the Press Me or Press Me, Too button is pressed, an alert is displayed.

Although we could achieve this same functionality by writing a single-view application, we're taking this more complex approach to demonstrate the mechanics of a multiview application. There are actually three view controllers interacting in this simple application: one that controls the blue view, one that controls the yellow view, and a third special controller that swaps the other two in and out when the Switch Views button is pressed.

Before we start building our application, let's talk a little bit about the way iPhone multiview applications are put together. Most multiview applications use the same basic pattern.

The Root Controller

The nib file is a key player here. For our View Switcher application, you'll find the file MainWindow.xib in your project window's Resources folder. That file contains the application delegate and the application's main window, along with the File's Owner and First Responder icons. We'll add an instance of a controller class that is responsible for managing which other view is currently being shown to the user. We call this controller the root controller (as in “the root of the tree” or “the root of all evil”) because it is the first controller the user sees and the controller that is loaded when the application loads. This root controller is often an instance of UINavigationController or UITabBarController, though it can also be a custom subclass of UIViewController.

In a multiview application, the job of the root controller is to take two or more other views and present them to the user as appropriate, based on the user's input. A tab bar controller, for example, will swap in different views and view controllers based on which tab bar item was last tapped. A navigation controller will do the same thing as the user drills down and backs up through hierarchical data.

NOTE: The root controller is the primary view controller for the application and, as such, is the view that specifies whether it is OK to automatically rotate to a new orientation. However, the root controller can pass responsibility for things like that to the currently active controller.

In multiview applications, most of the screen will be taken up by a content view, and each content view will have its own controller with its own outlets and actions. In a tab bar application, for example, taps on the tab bar will go to the tab bar controller, but taps anywhere else on the screen will go to the controller that corresponds to the content view currently being displayed.

Anatomy of a Content View

In a multiview application, each view controller controls a content view, and these content views are where the bulk of your application's user interface is built. Each content view generally consists of up to three pieces: the view controller, the nib, and a subclass of UIView. Unless you are doing something really unusual, your content view will always have an associated view controller, will usually have a nib, and will sometimes subclass UIView. Although you can create your interface in code rather than using a nib file, few people choose that route because it is more time-consuming and difficult to maintain. In this chapter, we'll be creating only a nib and a controller class for each content view.

In the View Switcher project, our root controller controls a content view that consists of a toolbar that occupies the bottom of the screen. The root controller then loads a blue view controller, placing the blue content view as a subview to the root controller view. When the root controller's switch views button is pressed (the button is in the toolbar), the root controller swaps out the blue view controller and swaps in a yellow view controller, instantiating that controller if it needs to do so. Confused? Don't worry, because this will become clearer as we walk through the code.

Building View Switcher

Enough theory! Let's go ahead and build our project. Select File Image New Project… or press ImageImageN. When the assistant opens, select Window-based Application (see Figure 6–9), and make sure the check box labeled Use Core Data for storageis unchecked and the Product popup button is set to iPhone. Type in a product name of View Switcher.

Image

Figure 6–9.Creating a new project using the Window-based Application project template

The template we just selected is actually even simpler than the View-based Application template we've been using up to now. This template will give us a window, an application delegate, and nothing else—no views, no controllers, no nothing.

Note: The window is the most basic container in iOS. Each app has exactly one window that belongs to it, though it is possible to see more than one window on the screen at a time. For example, if your app is running and a Short Message Service (SMS) message comes in, you'll see the SMS message displayed in its window. Your app can't access that overlaid window. It belongs to the SMS app.

You won't use the Window-based Application template very often when you're creating applications, but by starting from nothing, you'll really get a feel for the way multiview applications are put together.

Take a second to expand the Resourcesand Classesfolders in the Groups & Files pane and look at what's there. You'll find a single nib file, MainWindow.xib; the View_Switcher-Info.plistfile; and the two files in the Classes folder that implement the application delegate. Everything else we need for our application, we must create.

Creating Our View Controller and Nib Files

One of the more daunting aspects of building a multiview application from scratch is that we need to create several interconnected objects. We're going to create all the files that will make up our application before we do anything in Interface Builder and before we write any code. By creating all the files first, we'll be able to use Xcode's Code Sense to write our code faster. If a class hasn't been declared, Code Sense has no way to know about it, so we would need to type its name in full every time, which takes longer and is more error-prone.

Fortunately, in addition to project templates, Xcode also provides file templates for many standard file types, which helps simplify the process of creating the basic skeleton of our application.

Single-click the Classesfolder in theproject navigator, and then press ImageN or select File Image New File…. Take a look at the window that opens (see Figure 6–10).

Image

Figure 6–10.The template we'll use to create a new view controller subclass

If you select Cocoa Touch Classfrom the left pane, you will be given templates for a number of common Cocoa Touch classes. Select UIViewController subclass. In the middle-right pane, you'll see three check boxes:

  • The first is labeled Targeted for iPad, and should be unchecked (since we're not making an iPad GUI right now).
  • The second is labeled UITableViewController subclass, and would be helpful if we were going to create a table-based layout, but we're not, so make sure that's unchecked, too.
  • The third is labeled With XIB for user interface. If that box is checked, click it to uncheck it. If you select that option, Xcode will also create a nib file that corresponds to this controller class. We will start using that option in the next chapter, but for now, we want you to see how the different parts of the puzzle fit together by creating them all individually.

Click Next. A window appears that lets you name your file, specify if you'd like the .h file created to go along with the .m file, choose a particular directory in which to save the files, and pick a project and target for your files.

the usual file-saving window, which lets you choose where to save the file and what it should be called. For the sake of consistency, navigate into the Classes directory, which Xcode set up when you created this project; it should already contain the View_SwitcherAppDelegate class. That's where Xcode puts all of the Objective-C classes that are created as part of the project, and it's as good a place as any for you to put your own classes.

Name your new file SwitchViewController.mand make sure that Also create “SwitchViewController.h” is checked. Next, click the Choose… button and choose the project's Classes folder. Leave the project and target selections at their default settings and click the Finish button.

Xcode should add two files to your Classes folder. SwitchViewController will be your root controller—the controller that swaps the other views in and out. Now we need to create the controllers for the two content views that will be swapped in and out. Repeat the same steps two more times to create BlueViewController.m, YellowViewController.m, and their .h counterparts, adding them to the Classes group and saving them in the Classes folder in your project folder as well.

Caution: Make sure you check your spelling, as a typo here will create classes that don't match the source code later in the chapter.

Our next step is to create a nib file for each of the two content views we just created. Single-click the Resources folder in the project navigator, and then press ImageN or select File Image New File… again. This time, select User Interface under the iOS heading in the left pane (see Figure 6–11). Next, select the icon for the View XIBtemplate, which will create a nib with a content view. Then select iPhone from the Product popup, and click the Next button.

Image

Figure 6–11. We're creating a new nib file, using the View XIB template in the User Interface section.

When prompted for a file name, type BlueView.xib.

Now repeat the steps to create a second nib file called YellowView.xib. Once you've done that, you have all the files you need. It's time to start hooking everything together.

Modifying the App Delegate

Our first stop on the multiview express is the application delegate. Single-click the file View_SwitcherAppDelegate.h in the Groups & Files pane(make sure it's the app delegate and not SwitchViewcontroller.h), and make the following changes to that file:

#import <UIKit/UIKit.h>
@class SwitchViewController;
@interface View_SwitcherAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    SwitchViewController *switchViewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet SwitchViewController
    *switchViewController;
@end

The IBOutlet declaration you just typed is an outlet that will point to our application's root controller. We need this outlet because we are about to write code that will add the root controller's view to our application's main window when the application launches. We'll hook up that outlet when we edit MainWindow.xib.

Now, we need to add the root controller's view to our application's main window. Click View_SwitcherAppDelegate.m, and add the following code:

#import "View_SwitcherAppDelegate.h"
#import "SwitchViewController.h"
@implementation View_SwitcherAppDelegate

@synthesize window;
@synthesize switchViewController;

- (void) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    // Override point for customization after application launch
    [self.window addSubview:switchViewController.view];
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)dealloc {
    [window release];
    [switchViewController release];
    [super dealloc];
}

@end

Besides implementing the switchViewController outlet, we are adding the root controller's view to the window. Remember that the window is the only gateway to the user, so anything that needs to be displayed to the user must be added as a subview of the application's window.

If you go back to Chapter 5's Swap project and examine the code in SwapAppDelegate.m, you'll see that the template added the view controller's view to the application window for you. Since we're using a much simpler template for this project, we need to take care of that wiring together business ourselves.

SwitchViewController.h

Because we're going to be adding an instance of SwitchViewController to MainWindow.xib, now is the time to add any needed outlets or actions to the SwitchViewController.h header file.

We'll need one action method to toggle between the yellow and blue views. We won't need any outlets, but we will need two other pointers, one to each of the view controllers that we're going to be swapping in and out. These don't need to be outlets, because we're going to create them in code rather than in a nib. Add the following code to SwitchViewController.h:

#import <UIKit/UIKit.h>

@class YellowViewController;
@class BlueViewController;

@interface SwitchViewController : UIViewController {
    YellowViewController *yellowViewController;
    BlueViewController *blueViewController;
}
@property (retain, nonatomic) YellowViewController *yellowViewController;
@property (retain, nonatomic) BlueViewController *blueViewController;

-(IBAction)switchViews:(id)sender;
@end

Now that we've declared the action we need, we can add an instance of this class to MainWindow.xib.

Adding a View Controller

Save your source code, and double-click MainWindow.xib to open it in Interface Builder. Four icons should appear in the nib's main window: File's Owner, First Responder, View_SwitcherAppDelegate, and Window (see Figure 6–12). We need to add one more icon that will represent an instance of our root controller. Since Interface Builder's library doesn't have a SwitchViewController, we'll need to add a view controller and change its class to SwitchViewController.

Image

Figure 6–12. MainWindow.xib, showing File's Owner, First Responder, View_SwitcherAppDelegate, and Window

Since the class we need to add is a subclass of UIViewController, look in the library for a View Controller (see Figure 6–13), and drag one to the nib's main window (the window with the icons and the title MainWindow.xib).

Image

Figure 6–13. View Controller in the Library

Once you do this, your nib's main window will now have five icons, and a new window containing a dashed, gray, rounded rectangle labeled View should appear (see Figure 6–14).

Image

Figure 6–14. The window representing your view controller in Interface Builder

We just added an instance of UIViewController to our nib, but we actually need an instance of SwitchViewController, so let's change our view controller's class to SwitchViewController. Single-click the View Controller icon in the nib's main window, and press z4 to open the identity inspector (see Figure 6–15).

Image

Figure 6–15. Notice that the class is currently set to UIViewController in the identity inspector. We're about to change that.

The identity inspector allows you to specify the class of the currently selected object. Our view controller is currently specified as a UIViewController, and it has no actions defined. Click inside the combo box labeled Class, the one at the top of the inspector that currently reads UIViewController. Change the Class to SwitchViewController.

Once you make that change, you should notice that in the nib's main window, the name of our newly added View Controller has changed to Switch View Controller. Hold down the control key and click on the Switch View Controller icon in the main nib window. A summary popup window will appear (see Figure 6–16) giving a little detail about the clicked on object. Notice that the Received Actions section now lists the action switchViews:. This is your clue that the class of your view controller has indeed been changed to SwitchViewController. You'll see how we make use of this action in the next section.

Image

Figure 6–16.A control-click on the SwitchViewController brings up this popup window which shows a bit of detail about the clicked-on object.

CAUTION: If you don't see the switchViews: action in the popup window shown in Figure 6–16, check the spelling of your class file names. If you don't get the name exactly right, things won't match up. Watch your spelling!

Save your nib file and move to the next step.

Building a View with a Toolbar

We now need to build a view to add to SwitchViewController. As a reminder, this new view controller will be our root view controller—the controller that is in play when our application is launched. SwitchViewController's content view will consist of a toolbar that occupies the bottom of the screen. Its job is to switch between the blue view and the yellow view, so it will need a way for the user to change the views. For that, we're going to use a toolbar with a button. Let's build the toolbar view now.

Take a look back at the view controller shown in Figure 6–14. Notice the rounded gray rectangle with a dashed outline inside the view controller window. Find that same gray rectangle in Interface Builder. This gray rectangle represents the view controlled by that view controller. We're going to drag in a view to replace that gray rectangle. If you can't find that window, double-click on the Switch View Controller icon in the main nib window.

Drag a View from the library onto the gray rounded rect. The gray background should be replaced by this new view. As your cursor enters the gray rectangle, it will highlight, showing that it is receiving the drag (see Figure 6–17). Release the mouse button, and your new view will be in place.

Image

Figure 6–17. Dragging a View onto the SwitchViewController's view, replacing the default view

Now, let's add a toolbar to the bottom of the view. Grab a Toolbar from the library, drag it onto your view, and place it at the bottom, so that it looks like Figure 6–18.

Image

Figure 6–18. We dragged a Toolbar onto our new view. Notice that the Toolbar features a single button labeled Item.

The toolbar features a single button. We'll use that button to let the user switch between the different content views. Double-click the button, and change its title to Switch Views. Press the return key to commit your change.

Now, we can link the toolbar button to our action method. Before doing that, though, we should warn you: toolbar buttons aren't like other iOS controls. They support only a single target action, and they trigger that action only at one well-defined moment—the equivalent of a Touch Up Inside event on other iOS controls.

Selecting a toolbar button in Interface Builder can be tricky. Click the view so we are all starting in the same place. Now single-click the toolbar button. Notice that this selects the toolbar, and not the button. Now, click the button a second time. This should select the button itself. You can confirm you have the button selected by switching to the attributes inspector (Image1) and making sure it says Bar Button Item.

Once you have the Switch Views button selected, control-drag from it over to the Switch View Controller icon, and select the switchViews: action. If the switchViews: action doesn't pop up and instead you see an outlet called delegate, you've most likely control-dragged from the toolbar rather than the button. To fix it, just make sure you have the button and not the toolbar selected, and then redo your control-drag.

Tip. Remember, you can always view the main nib window in list mode and use the disclosure triangles to drill down through the hierarchy to get to any element in the view hierarchy.

Earlier, we created an outlet in View_SwitcherAppDelegate.hso our application could get to our instance of SwitchViewController and add its view to the main application window. Now, we need to connect the instance of SwitchViewController in our nib to that outlet. Control-drag from the ViewSwitcher App Delegate icon to the Switch View Controller icon, and select the switchViewController outlet. You may see a second outlet called viewController. If you do, make sure you connect to switchViewControllerand not viewController.

That's all we need to do here, so save your nib file. Next, let's get started implementing SwitchViewController.

Writing the Root View Controller

It's time to write our root view controller. Its job is to switch between the yellow view and the blue view whenever the user clicks the Switch Views button.

Making the following changes to SwitchViewController.m. You can delete the commented-out methods provided by the template if you want to shorten the code.

#import "SwitchViewController.h"
#import "YellowViewController.h"
#import "BlueViewController.h"

@implementation SwitchViewController
@synthesize yellowViewController;
@synthesize blueViewController;

- (void)viewDidLoad
{
    BlueViewController *blueController = [[BlueViewController alloc]
              initWithNibName:@"BlueView" bundle:nil];
    self.blueViewController = blueController;
    [self.view insertSubview:blueController.view atIndex:0];
    [blueController release];
    [super viewDidLoad];
}

- (IBAction)switchViews:(id)sender
{
    if (self.yellowViewController.view.superview == nil)
   {
        if (self.yellowViewController == nil)
        {
            YellowViewController *yellowController =
            [[YellowViewController alloc] initWithNibName:@"YellowView"
                                                   bundle:nil];
            self.yellowViewController = yellowController;
            [yellowController release];
        }
        [blueViewController.view removeFromSuperview];
        [self.view insertSubview:yellowViewController.view atIndex:0];
   }
   else
   {
      if (self.blueViewController == nil)
      {
          BlueViewController *blueController =
          [[BlueViewController alloc] initWithNibName:@"BlueView"
                                               bundle:nil];
          self.blueViewController = blueController;
          [blueController release];
      }
      [yellowViewController.view removeFromSuperview];
      [self.view insertSubview:blueViewController.view atIndex:0];
   }
}
...

Also, add the following code to the existing didReceiveMemoryWarning method:

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc, that aren't in use
    if (self.blueViewController.view.superview == nil)
        self.blueViewController = nil;
    else
       self.yellowViewController = nil;
}

Then add the following two statements to the dealloc method:

- (void)dealloc {
    [yellowViewController release];
    [blueViewController release];
    [super dealloc];
}
@end

The first method we added, viewDidLoad, overrides a UIViewController method that is called when the nib is loaded. How could we tell? Option-double-click the method name, and take a look at the documentation window that appears (see Figure 6–19). The method is defined in our superclass and is intended to be overridden by classes that need to be notified when the view has finished loading.

Image

Figure 6–19. This documentation window appears when you option-double-click the viewDidLoad method. Note the reference to SwitchViewController in the window title.

This version of viewDidLoad creates an instance of BlueViewController. We use the initWithNibName method to load the BlueViewController instance from the nib file BlueView.xib. Note that the file name provided to initWithNibName does not include the .xibextension. Once the BlueViewController is created, we assign this new instance to our blueViewController property.

    BlueViewController *blueController = [[BlueViewController alloc]
          initWithNibName:@"BlueView" bundle:nil];
    self.blueViewController = blueController;

Next, we insert the blue view as a subview of the root view. We insert it at index 0, which tells iOS to put this view behind everything else. Sending the view to the back ensures that the toolbar we created in Interface Builder a moment ago will always be visible on the screen, since we're inserting the content views behind it.

[self.view insertSubview:blueController.view atIndex:0];

Now, why didn't we load the yellow view here also? We're going to need to load it at some point, so why not do it now? Good question. The answer is that the user may never tap the Switch Views button. The user might just use the view that's visible when the application launches, and then quit. In that case, why use resources to load the yellow view and its controller?

Instead, we'll load the yellow view the first time we actually need it. This is called lazy loading, and it's a standard way of keeping memory overhead down. The actual loading of the yellow view happens in the switchViews: method, so let's take a look at that.

switchViews: first checks which view is being swapped in by seeing whether yellowViewController's view's superview is nil. This will return true if one of two things is true:

  • If yellowViewController exists but its view is not being shown to the user, that view will have no superview because it's not presently in the view hierarchy, and the expression will evaluate to true.
  • If yellowViewController doesn't exist because it hasn't been created yet or was flushed from memory, it will also return true.

We then check to see whether yellowViewController is nil.

   if (self.yellowViewController.view.superview == nil)
   {

If it is, that means there is no instance of yellowViewController, and we need to create one. This could happen because it's the first time the button has been pressed or because the system ran low on memory and it was flushed. In this case, we need to create an instance of YellowViewController as we did for the BlueViewController in the viewDidLoad method:

       if (self.yellowViewController == nil)
       {
           YellowViewController *yellowController =
           [[YellowViewController alloc] initWithNibName:@"YellowView"
                                                  bundle:nil];
           self.yellowViewController = yellowController;
           [yellowController release];
       }

At this point, we know that we have a yellowViewController instance, because either we already had one or we just created it. We then remove blueViewController's view from the view hierarchy and add yellowViewController's view:

      [blueViewController.view removeFromSuperview];
      [self.view insertSubview:yellowViewController.view atIndex:0];
   }

If self.yellowViewController.view.superview is not nil, then we need to do the same thing, but for blueViewController. Although we create an instance of BlueViewController in viewDidLoad, it is still possible that the instance has been flushed because memory got low. Now, in this application, the chances of memory running out are slim, but we're still going to be good memory citizens and make sure we have an instance before proceeding:

   else
   {
       if (self.blueViewController == nil)
       {
           BlueViewController *blueController =
           [[BlueViewController alloc] initWithNibName:@"BlueView"
                                                bundle:nil];
           self.blueViewController = blueController;
          [blueController release];
       }
       [yellowViewController.view removeFromSuperview];
       [self.view insertSubview:blueViewController.view atIndex:0];
   }

In addition to not using resources for the yellow view and controller if the Switch Views button is never tapped, lazy loading also gives us the ability to release whichever view is not being shown to free up its memory. iOS will call the UIViewController method didReceiveMemoryWarning, which is inherited by every view controller, when memory drops below a system-determined level.

Since we know that either view will be reloaded the next time it is shown to the user, we can safely release either controller. We do this by adding a few lines to the existing didReceiveMemoryWarning method:

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning]; // Releases the view if it
                                     // doesn't have a superview
    // Release anything that's not essential, such as cached data
    if (self.blueViewController.view.superview == nil)
        self.blueViewController = nil;
    else
        self.yellowViewController = nil;
}

This newly added code checks to see which view is currently being shown to the user and releases the controller for the other view by assigning nil to its property. This will cause the controller, along with the view it controls, to be deallocated, freeing up its memory.

TIP: Lazy loading is a key component of resource management on iOS, and you should implement it anywhere you can. In a complex, multiview application, being responsible and flushing unused objects from memory can be the difference between an application that works well and one that crashes periodically because it runs out of memory.

Implementing the Content Views

The two content views that we are creating in this application are extremely simple. They each have one action method that is triggered by a button, and neither one needs any outlets. The two views are also nearly identical. In fact, they are so similar that they could have been represented by the same class. We chose to make them two separate classes because that's how most multiview applications are constructed. Let's declare an action method in each of the header files. First, in BlueViewController.h, add the following declaration:

#import <UIKit/UIKit.h>
@interface BlueViewController : UIViewController {

}
- (IBAction)blueButtonPressed;
@end

Save it, and then add the following line to YellowViewController.h:

#import <UIKit/UIKit.h>

@interface YellowViewController : UIViewController {
}
- (IBAction)yellowButtonPressed;
@end

Save this file as well, and then double click BlueView.xib to open it in Interface Builder so we can make a few changes. First, we need to specify that the class that will load this nib from disk is BlueViewController. Single-click the File's Owner icon and press Image4 to bring up the identity inspector. File's Owner defaults to NSObject; change it to BlueViewController.

Single-click the View icon in the dock and then press Image1 to bring up the attribute inspector. In the inspector's View section, click the color well that's labeled Background, and use the popup color picker to change the background color of this view to a nice shade of blue. Once you are happy with your blue, close the color picker.

Next, we'll change the size of the view in the nib. In the attributes inspector, the top section is labeled Simulated User Interface Elements. If we set these drop-downs to reflect which top and bottom elements are used in our application, Interface Builder will automatically calculate the size of the remaining space. The status bar is already specified. If you select the Bottom Bar popup, you can select Toolbar to indicate that the enclosing view has a toolbar.

Image

Figure 6–20.The Simulated Metrics section of the View's attributes inspector

Setting this will cause Interface Builder to calculate the correct size for your view automatically, so that you know how much space you have to work with. You can press Image3 to bring up the size inspector to confirm this. After making the change, the height of the window should be 416 pixels, and the width should still be 320 pixels.

Drag a Round Rect Button from the library over to the. Double-click the button, and change its title to Press Me. You can place the button anywhere that looks good to you. Next, with the button still selected, switch to the connections inspector (by pressing Image2), drag from the Touch Up Inside event to the File's Owner icon, and connect to the blueButtonPressed action method.

We have one more thing to do in this nib, which is to connect the BlueViewController's view outlet to the view in the nib. The view outlet is inherited from the parent class, UIViewController, and gives the controller access to the view it controls. When we changed the underlying class of the file's owner, the existing outlet connections were broken. So, we need to reestablish the connection from the controller to its view. Control-drag from the File's Owner icon to the View icon, and select the view outlet to do that.

Save the nib, go back to Xcode, and double click YellowView.xib. We're going to make almost exactly the same changes to this nib file.

First, click the File's Owner icon in the nib window and use the identity inspector to change its class to YellowViewController.

Next, select the view and switch to the attributes inspector. In the attributes inspector, click the Background color well and select a bright yellow, and then close the color picker. Also, select Toolbar from the Bottom Bar popup in the Simulated User Interface Elements section.

Next, drag out a Round Rect Button from the library and center it on the view, then change its title to Press Me, Too. With the button still selected, use the connections inspector to drag from the Touch Up Inside event to the File's Owner icon, and connect to the yellowButtonPressed action method.

Finally, control-drag from the File's Owner icon to the View icon, and select the view outlet.

Once all that is done, save the nib, and get ready to enter some more code.

The two action methods we're going to implement do nothing more than show an alert (as we did in Chapter 4's Control Fun application), so go ahead and add the following code to BlueViewController.m:

#import "BlueViewController.h"

@implementation BlueViewController

- (IBAction)blueButtonPressed
{
    UIAlertView *alert = [[UIAlertView alloc]
        initWithTitle:@"Blue View Button Pressed"
              message:@"You pressed the button on the blue view"
             delegate:nil
    cancelButtonTitle:@"Yep, I did."
    otherButtonTitles:nil];
    [alert show];
    [alert release];
}
...

Save the file. Next, switch over to YellowViewController.m, and add this very similar code to that file:

#import "YellowViewController.h"

@implementation YellowViewController

- (IBAction)yellowButtonPressed
{
   UIAlertView *alert = [[UIAlertView alloc]
       initWithTitle:@"Yellow View Button Pressed"
             message:@"You pressed the button on the yellow view"
            delegate:nil
   cancelButtonTitle:@"Yep, I did."
   otherButtonTitles:nil];
   [alert show];
   [alert release];
}
...

Save your code, and let's take this bad boy for a spin. When our application launches, it shows the view we built in BlueView.xib. When you tap the Switch Views button, it will change to show the view that we built in YellowView.xib. Tap it again, and it goes back to the view we built in BlueView.xib. If you tap the button centered on the blue or yellow view, you'll get an alert view with a message indicating which button was pressed. This alert shows that the correct controller class is being called for the view that is being shown.

The transition between the two views is kind of abrupt, though. Gosh, if only there were some way to make the transition look nicer.

Animating the Transition

Of course, there is a way to make the transition look nicer! We can animate the transition in order to give the user visual feedback of the change. UIView has several class methods we can call to indicate that the transition should be animated, to indicate the type of transition that should be used, and to specify how long the transition should take.

Go back to SwitchViewController.m, and replace your switchViews: method with this new version:

- (IBAction)switchViews:(id)sender
{
    [UIView beginAnimations:@"View Flip" context:nil];
    [UIView setAnimationDuration:1.25];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

    if (self.yellowViewController.view.superview == nil)
    {
        if (self.yellowViewController == nil)
        {
            YellowViewController *yellowController =
            [[YellowViewController alloc] initWithNibName:@"YellowView"
                                                   bundle:nil];
            self.yellowViewController = yellowController;
            [yellowController release];
        }
        [UIView setAnimationTransition:
         UIViewAnimationTransitionFlipFromRight
                               forView:self.view cache:YES];

        [blueViewController viewWillAppear:YES];
        [yellowViewController viewWillDisappear:YES];

        [blueViewController.view removeFromSuperview];
        [self.view insertSubview:yellowViewController.view atIndex:0];
        [yellowViewController viewDidDisappear:YES];
        [blueViewController viewDidAppear:YES];
    }
    else
    {
       if (self.blueViewController == nil)
       {
          BlueViewController *blueController =
          [[BlueViewController alloc] initWithNibName:@"BlueView"
                                               bundle:nil];
          self.blueViewController = blueController;
          [blueController release];
       }
        [UIView setAnimationTransition:
         UIViewAnimationTransitionFlipFromLeft
                             forView:self.view cache:YES];

        [yellowViewController viewWillAppear:YES];
        [blueViewController viewWillDisappear:YES];

        [yellowViewController.view removeFromSuperview];
        [self.view insertSubview:blueViewController.view atIndex:0];
        [blueViewController viewDidDisappear:YES];
        [yellowViewController viewDidAppear:YES];
    }
    [UIView commitAnimations];
}

Compile this new version, and run your application. When you tap the Switch Views button, instead of the new view just snapping into place, the old view will flip over to reveal the new view, as shown in Figure 6–21.

Image

Figure 6–21. One view transitioning to another, using the flip style of animation

In order to tell iOS that we want a change animated, we need to declare an animation block and specify how long the animation should take. Animation blocks are declared by using the UIView class method beginAnimations:context:, like so:

    [UIView beginAnimations:@"View Flip" context:NULL];
    [UIView setAnimationDuration:1.25];

beginAnimations:context: takes two parameters. The first is an animation block title. This title comes into play only if you take more direct advantage of Core Animation, the framework behind this animation. For our purposes, we could have used nil. The second parameter is a (void *) that allows you to specify an object (or any other C data type) whose pointer you would like associated with this animation block. We used NULL here, since we don't need to do that.

After that, we set the animation curve, which determines the timing of the animation. The default, which is a linear curve, causes the animation to happen at a constant speed. The option we set here, UIViewAnimationCurveEaseInOut, specifies that the animation should start slow but speed up in the middle, slowing down again at the end. This gives the animation a more natural, less mechanical appearance.

    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

Next, we need to specify the transition to use. At the time of this writing, four view transitions are available on iOS:

  • UIViewAnimationTransitionFlipFromLeft
  • UIViewAnimationTransitionFlipFromRight
  • UIViewAnimationTransitionCurlUp
  • UIViewAnimationTransitionCurlDown

We chose to use two different effects, depending on which view was being swapped in. Using a left flip for one transition and a right flip for the other makes the view seem to flip back and forth. The cache option speeds up drawing by taking a snapshot of the view when the animation begins and using that image, rather than redrawing the view at each step of the animation. You should always have it cache the animation unless the appearance of the view may need to change during the animation.

    [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight
                       forView:self.view cache:YES];

After we set the transition, we make two calls, one on each of the views being used in the transition:

    [self.blueViewController viewWillAppear:YES];
    [self.yellowViewController viewWillDisappear:YES];

When we're finished swapping the views, we make two more calls on those views:

    [self.yellowViewController viewDidDisappear:YES];
    [self.blueViewController viewDidAppear:YES];

The default implementations of these methods in UIViewController do nothing, so our calls to viewDidDisappear: and viewDidAppear: don't do anything, since our controllers didn't override those methods. It's important to make these calls even if you know you're not using them.

Why is it important to make these calls even though they do nothing? Although we're not using those methods now, we might choose to in the future. It's also possible that UIViewController‘s implementation to those methods won't always be empty, so failing to call these methods could cause our application to behave oddly after a future update of the operating system. The performance hit for making these four calls is meaningless, since they trigger no code, and by putting them in, we can be sure that our application will continue to work. We're future-proofing.

When we're finished specifying the changes to be animated, we call commitAnimations on UIView. Everything between the start of the animation block and the call to commitAnimations will be animated together.

Thanks to Cocoa Touch's use of Core Animation under the hood, we're able to do fairly sophisticated animation with only a handful of code.

Switching Off

Whoo-boy! Creating our own multiview controller was a lot of work, wasn't it? You should have a very good grasp on how multiview applications are put together now that you've built one from scratch. Although Xcode contains project templates for the most common types of multiview applications, you need to understand the overall structure of these types of applications so you can build them yourself from the ground up. The delivered templates are incredible time-savers, but at times, they simply won't meet your needs.

In the next three chapters, we're going to continue building multiview applications to reinforce the concepts from this chapter and to give you a feel for how more complex applications are put together. In the next chapter, we'll construct a tab bar application, and in the two chapters after that, we'll build a navigation-based application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset