iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (85 page)

Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online

Authors: Aaron Hillegass,Joe Conway

Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming

BOOK: iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides)
13.57Mb size Format: txt, pdf, ePub

Notice that here again you have left a non-split view controller, non-iPad option in an
else
clause that pushes the
ChannelViewController
onto the navigation controller’s stack.

 

Build and run the application. After the RSS feed loads, tap the
Info
button. The detail view controller will display the metadata for the channel. However, if you tap on a post after you’ve loaded the metadata, nothing will happen – you can’t get back to a web view.

 

This is because the navigation controller that held the web view controller was only owned by the split view controller. So when we replaced it with a navigation controller holding the channel view controller in
showInfo:
, the first navigation controller was destroyed. We have to create another navigation controller that holds the web view controller and give it to the split view controller.

 

In
ListViewController.m
, modify the
tableView:didSelectRowAtIndexPath:
to place a navigation controller with the
WebViewController
in the split view controller.

 
- (void)tableView:(UITableView *)tableView
    didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (![self splitViewController])
        [[self navigationController] pushViewController:webViewController
                                               animated:YES];
    else {
        // We have to create a new navigation controller, as the old one
        // was only retained by the split view controller and is now gone
        UINavigationController *nav =
        [[UINavigationController alloc] initWithRootViewController:webViewController];
        NSArray *vcs = [NSArray arrayWithObjects:[self navigationController],
                                                 nav,
                                                 nil];
        [[self splitViewController] setViewControllers:vcs];
        // Make the detail view controller the delegate of the split view controller
        // - ignore this warning
        [[self splitViewController] setDelegate:webViewController];
    }
    RSSItem *entry = [[channel items] objectAtIndex:[indexPath row]];
    [webViewController listViewController:self handleObject:entry];
}
 

Build and run the application. You should be able to move back and forth between the two detail view controllers.

 

The
ListViewController
doesn’t know how to show a post in a web view or show the info for the
RSSChannel
. So it
delegates
those behaviors to other objects.
ListViewController
does know about the
ListViewControllerDelegate
protocol, and it sends that protocol’s message to a conforming detailed view controller to handle things it can’t.

 

Even though the
ListViewController
never sets either of the detail view controllers as its
delegate
, this is still delegation. Delegation is a design pattern, not a naming convention. For another example, the table view-data source relationship is delegation, even though the variable the table view sends messages to is called
dataSource
.

 
Displaying the Master View Controller in Portrait Mode

While in portrait mode, the master view controller is missing in action. It would be nice if you could see the master view controller to select a new post from the list without having to rotate the device.
UISplitViewController
lets you do just that by supplying its delegate with a
UIBarButtonItem
. Tapping this button shows the master view controller in a
UIPopoverController
(
Figure 26.5
).

 

Figure 26.5  Master view controller in UIPopoverController

 

In your code, whenever a detail view controller was given to the split view controller, that detail view controller was set as the split view controller’s delegate. As the delegate, the detail view controller will get a pointer to the
UIBarButtonItem
when rotating to portrait mode.

 

Since both
WebViewController
and
ChannelViewController
can be the delegate for a
UISplitViewController
, it’s best to declare that they conform to the
UISplitViewControllerDelegate
protocol.

 

In
WebViewController.h
, add this declaration:

 
@interface WebViewController : UIViewController
            , UISplitViewControllerDelegate
>
 

And do the same in
ChannelViewController.h
.

 
@interface ChannelViewController : UITableViewController
                , UISplitViewControllerDelegate
>

Build and run the application. The behavior will be the same, but there won’t be any warnings.

 

In
WebViewController.m
, implement the following delegate method to place the bar button item in the
WebViewController
’s navigation item.

 
- (void)splitViewController:(UISplitViewController *)svc
     willHideViewController:(UIViewController *)aViewController
          withBarButtonItem:(UIBarButtonItem *)barButtonItem
       forPopoverController:(UIPopoverController *)pc
{
    // If this bar button item doesn't have a title, it won't appear at all.
    [barButtonItem setTitle:@"List"];
    // Take this bar button item and put it on the left side of our nav item.
    [[self navigationItem] setLeftBarButtonItem:barButtonItem];
}

Notice that we explicitly set the title of the button. If the button doesn’t have a title, it won’t appear at all. (If the master view controller’s
navigationItem
has a title, then the button will be automatically set to that title. )

 

Build and run the application. Rotate to portrait mode, and you will see the bar button item appear on the left of the navigation bar. Tap that button, and the master view controller’s view will appear in a
UIPopoverController
.

 

This bar button item is why we always had you put the detail view controller inside a navigation controller. You don’t have to use a navigation controller to put a view controller in a split view controller, but it makes using the bar button item much easier. (If you don’t use a navigation controller, you can instantiate your own
UINavigationBar
or
UIToolbar
to hold the bar button item and add it as a subview of the
WebViewController
’s view.)

 

There are two small issues left to address with your
List
button. First, when the device is rotated back to landscape mode, the button is still there. To remove it, the delegate needs to respond to another message from the
UISplitViewController
. Implement this delegate method in
WebViewController.m
.

 
- (void)splitViewController:(UISplitViewController *)svc
     willShowViewController:(UIViewController *)aViewController
  invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
    // Remove the bar button item from our navigation item
    // We'll double check that its the correct button, even though we know it is
    if (barButtonItem == [[self navigationItem] leftBarButtonItem])
        [[self navigationItem] setLeftBarButtonItem:nil];
}

Build and run the application. The
List
button will now appear and disappear as you rotate between portrait and landscape modes.

 

The second issue is that the
ChannelViewController
also needs to show the
List
button. In
ChannelViewController.m
, implement the two
UISplitViewControllerDelegate
methods.

 
- (void)splitViewController:(UISplitViewController *)svc
     willHideViewController:(UIViewController *)aViewController
          withBarButtonItem:(UIBarButtonItem *)barButtonItem
       forPopoverController:(UIPopoverController *)pc
{
    [barButtonItem setTitle:@"List"];
    [[self navigationItem] setLeftBarButtonItem:barButtonItem];
}
- (void)splitViewController:(UISplitViewController *)svc
     willShowViewController:(UIViewController *)aViewController
  invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
    if (barButtonItem == [[self navigationItem] leftBarButtonItem])
        [[self navigationItem] setLeftBarButtonItem:nil];
}

Build and run the application. Now the
List
button will also appear on the navigation bar when the
ChannelViewController
is on the screen.

 

(There is a third issue: while in portrait mode, if you switch between the
ChannelViewController
and the
WebViewController
, you lose the bar button item. Fixing this problem is one of the challenges at the end of this chapter.)

 
Universalizing Nerdfeed

When first creating
Nerdfeed
, we chose to go with an iPad-only application. Now let’s turn it into a universal application. Select the
Nerdfeed
project from the project navigator. In the editor area, choose the
Nerdfeed
target and then the
Summary
tab.

 

Figure 26.6  Universalizing Nerdfeed

 

From the
Devices
pop-up menu, choose
Universal
.

 

Figure 26.7  Changing simulators

 

That’s all there is to it – the application is now universal. You can test it by building and running again on one simulator and then the other.

 

There are two reasons the universalization process was so easy for
Nerdfeed
. Remembering these reasons will help you when you’re writing your own applications.

 
  • As we built
    Nerdfeed
    , we were mindful of the device differences in the classes we used. For example, knowing that a
    UISplitViewController
    doesn’t exist on the iPhone or iPod touch, we made sure that there was an alternative interface on those devices. In general, when using an Apple-provided class, you should read the discussion in the documentation about that class. It will give you tips about the availability of the class and its methods on the different devices.
 
  • Nerdfeed
    is still a relatively simple application. It is always easier to universalize an application early in development. As an application grows, its details get buried in the massive pile of code. Finding and fixing issues as you’re writing code is much easier than coming back later. Details are harder to find, and there is the risk of breaking what already works.
 

Other books

The Tiger in the Well by Philip Pullman
Man Eaters by Linda Kay Silva
Personal Adventures by Bristol, Sidney
Taken in Hand by Barbara Westbrook
Shadow by Mark Robson