iOS 6 Application Development For Dummies (2013)

Part V. Adding the Application Content

Chapter 16. Displaying Events Using a Page View Controller

In This Chapter

arrow Displaying HTML pages in a Web view

arrow Creating page transitions with a UIPageViewController

arrow Understanding how Page View controllers work and implementing page turns

After you have finished this chapter, when the user selects Events from the Master view in the RoadTrip application, he or she will come face-to-face with a series of pages that describes the latest activities happening at his destination. In this chapter, I’ll show you how to use a feature, first introduced in iOS 5, that will allow you to create a view controller that enables a user to “turn event pages” in the same way as he or she can in an iBook.

You will also find out how to use a Web view again to display data, but this time you will download an HTML page stored on my website, rather than download a website itself.

The best part of what you discover in this chapter is page-turn transitions. These transitions are implemented by Apple’s UIPageViewController class — a new container view controller (first provided in iOS 5) that creates page-turn transitions between view controllers. Just as a Navigation controller animates the transitions between, say, the Master View controller and the Test Drive controller in Chapter 5, the Page View controller does its thing between two view controllers — in this case, two EventPageControllers.

You implement this functionality by adding a UIPageViewController to your view controller — EventsController — in your code and then creating a view controller (EventPageController) for each page.

As I mention in Chapter 13, the UIPageViewController needs a Navigation bar. You need it if you want to be able to display the Road Trip button. Because of the way the class is implemented, tapping a button on a toolbar is intercepted and interpreted by the UIPageViewController as a page turn. Fortunately, you made it possible for the delegate methods to deal with both Navigation bars and toolbars with equal aplomb.

The Plan

The Events feature actually requires that you add a number of interrelated components to each storyboard file. The components used in the iPad storyboard are shown in Figure 16-1.

image

Figure 16-1: Components used in the iPad storyboard file for the Events feature.

The key components for the Events feature include

image The “Events” Table view cell in the RTMasterViewController’s Table view.

image A Replace segue from the “Events” Table view cell to a Navigation controller that displays a Navigation bar-equipped view controller in the detail view. This bar contains a RoadTrip button used by the user to return from the Events display to the RTMasterViewController.

image A custom EventsController container.

image An instance of UIPageViewController embedded in the EventsController container, responsible for managing transitions (such as page curls) between events pages, where each page is managed by an instance of EventPageController.

image A custom EventPageController, responsible for displaying event information in an HTML web page rendered by an instance of UIWebView.

Setting Up the EventsController

In this section, you need to do the same thing you did in Chapter 15 to create and connect the EventsController object. As I explain in Chapter 15, the way you develop a storyboard is rather formulaic. I review it here (and for the last time):

1. Lay out the view controllers you need for the user experience architecture.

2. Add the custom view controller to your app.

3. Tie the two together in your storyboard.

4. Add the code you need to the custom view controller.

After you get into the routine of how to do it, your life as a developer becomes much easier.

You’ll also add another view controller to the storyboard (the aforementioned EventPageController) that will be used by the UIPageViewController.

Adding the custom view controller

To add the EventsController to the RoadTrip project, follow these steps:

1. In the Project navigator, select the View Controller Classes group and then either right-click the selection and choose New File from the menu that appears or choose FileNewNew File from the main menu (or press Command Key+N).

Whatever method you choose, you’re greeted by the New File dialog.

2. In the left column of the dialog, select Cocoa Touch under the iOS heading, select the Objective-C class template in the top-right pane, and then click Next.

You’ll see a dialog that will enable you to choose the options for your file.

3. Enter EventsController in the Class field, choose or enter RTDetailViewController in the Subclass Of field, make sure that the Target for iPad check box is selected and that With XIB for User Interface is deselected, and then click Next.

4. In the Save sheet that appears, click Create.

You’ll also need to create a controller that manages each event page. Follow these steps:

1. In the Project navigator, select the View Controller Classes group and then either right-click the selection and choose New File from the menu that appears or choose FileNewNew File from the main menu (or press Command Key+N).

Say hello again to the New File dialog box.

2. In the left column of the dialog, select Cocoa Touch under the iOS heading, select the Objective-C class template in the top-right pane, and then click Next.

You’ll see a dialog that will enable you to choose the options for your file.

3. Enter EventPageController in the Class field, choose or enter UIViewController in the Subclass Of drop-down menu, and make sure that the Target for iPad check box is selected and that With XIB for User Interface is deselected. Click Next.

4. In the Save sheet that appears, click Create.

Setting up the EventsController in the MainStoryboard

Just as you did in Chapter 15, you need to tell the storyboard to load your custom view controller rather than a UIViewController. Follow these steps:

1. In the Project navigator, select MainStoryboard_iPad and, in the Document Outline, select View Controller – Events in the View Controller – Events Scene.

The Events view controller will be selected on the canvas.

image  2. Open the Utility area and then click the Identity Inspector icon in the Inspector selector to open the Identity inspector in the Utility area.

3. Choose EventsController from the Class drop-down menu (replacing UIViewController) in the Custom Class section.

While in WeatherController, you added a Web view, but you won’t be doing that here. You also created an outlet, but you don’t need that here, either. Instead, you’ll use a Web view and create an outlet in the EventPageController. The EventPageController is what you’ll need to add to implement a UIPageViewController. You do that in the next section.

Adding and setting up the EventPageController in the MainStoryboard

You need a view controller to manage each view within the Page View controller. Although you could’ve added this view controller when you extended the storyboard, I didn’t have you do so because I didn’t want my coverage of the topic to get lost among the discussion about segues.

To add the EventPageController to the storyboard, follow these steps:

1. Add another view controller to the storyboard by dragging in a view controller from the Library pane and placing it next to the EventsController on the Canvas.

(You don’t have to put it there, but doing so hints that a relationship exists.)

image  2. Open the Identity inspector in the Utility area using the Inspector selector bar, and in the Class drop-down menu in the Custom Class section, choose EventPageController (replacing UIViewController).

image  3. Switch to the Attributes inspector and use its text fields to give the controller the Title of EventPage. Then add EventPage to the Identity inspector’s Storyboard ID field.

4. Add a Web view to the EventPageController by dragging in a Web view from the Library pane and into the Event Page controller, just as you did in Chapter 15.

The Event Page view will be a Web view because you’ll want it to download and then display an HTML page.

As you may recall from Chapter 15, the UIWebView class provides a way to display HTML content and has the built-in functionality to download HTML content from the web.

image  5. Click the Size inspector icon in the Inspector selector to open the Size inspector in the Utility area.

Just as you did with the Web view in Chapter 14, you are going to have to adjust the Autosizing settings.

6. Click on all the currently disabled springs (the two indicators, each with an arrowhead at each end in the inner square) and struts (the indicators with two perpendicular lines at each end) to enable them.

Note that when they’re enabled, they’re solid and bright red, and when disabled, dim and dashed.

If you are unclear about why you are doing this, refer to Chapter 15.

7. Drag in an Activity Indicator view from the Library pane and center it in the view.

Because these pages can be large and take some amount of time to download, you want to have some kind of Activity Indicator view to let the user know that the application is still running but busy, as opposed to frozen.

imageAs you can see by looking at the Document Outline in Figure 16-2, both the Web view and Activity Indicator view are siblings — and subviews of the view. It’s important that both are siblings, and that the Activity Indicator view is below the Web view in order for it to display. (Remember the Last-One-In-Is-On-Top principle from Chapter 4 when it comes to subviews.) If that’s not the case, rearrange the views in the Document Outline.

image  8. Switch to the Size inspector in the Utility area using the Inspector selector, and then update the Autosizing values for the Activity indicator by simply deselecting all the struts and springs to keep it in the middle, as shown in Figure 16-2).

image  9. Close the Utility area and select the Assistant from the Editor selector in the toolbar. If the EventPageController.m implementation file isn’t the one that’s displayed, go up to the Assistant’s Jump bar and select it.

10. Control-drag from the Web view in either the Canvas or the Document Outline to the EventPageController interface and create an IBOutlet (just as you do in Chapter 15) named eventDataView.

image

Figure 16-2: A Web view with an activity indicator.

11. Control-drag from the Activity Indicator view to the EventPageController interface and create an IBOutlet named activityIndicator.

12. Working within the Document Outline, control-drag from the Web view to the Event Page controller, and then select Delegate from the Outlets menu that appears.

This will make EventPageController the Web view delegate.

Extending the Trip Model

The EventsController will need two pieces of information from the Trip model: the number of events and the URL for a specific event.

Add the declaration for the two Events methods (bolded) to the Trip interface in Trip.h, as shown in Listing 16-1.

Listing 16-1: Update the Trip Interface

@interface Trip : NSObject

- (id)initWithDestinationIndex:(int)destinationIndex;

- (UIImage *) destinationImage;

- (NSString *) destinationName;

- (CLLocationCoordinate2D) destinationCoordinate;

- (NSString *)weather;

- (NSUInteger)numberOfEvents;

- (NSString *)getEvent:(NSUInteger)index;

But Trip isn’t going to go at this alone (as it did with Weather). It will use an Events object (which you’ll create shortly). So that Trip can use the Event object, add the bolded code in Listing 16-2 to Trip.m.

Listing 16-2: Updating the Trip Implementation

#import “Trip.h”

#import “Destination.h”

#import “Events.h”

@interface Trip () {

  NSDictionary *destinationData;

  Destination* destination;

  Events *events;

}

@end

imageUntil you add the Events class in the next section, you’ll see some Live Issues errors.

The EventsController, as you will see, will need to know the number of events and also get the event information.

Add the implementation of methods you need in Listings 16-3 and 16-4 to Trip.m.

Listing 16-3: The Number of Events

- (NSUInteger)numberOfEvents {

  return [events numberOfEvents];

}

Listing 16-4: Get an Event

- (NSString *)getEvent:(NSUInteger)index {

  return [events getEvent:index];

}

To have Trip create the required Events object, add the bolded code in Listing 16-5 to initWithDestinationIndex: in Trip.m.

Listing 16-5: Updating initWithDestinationIndex:

- (id)initWithDestinationIndex:

                           (NSUInteger)destinationIndex {

self = [super init];

  if (self) {

    NSString *filePath = [[NSBundle mainBundle]

        pathForResource:@”Destinations” ofType:@”plist”];  

    NSDictionary *destinations = [NSDictionary dictionaryWithContentsOfFile: filePath];

    NSArray *destinationsArray =

           destinations[@”DestinationData”];

    destinationData = destinationsArray[destinationIndex];

    destination = [[Destination alloc] initWithDestinationIndex:destinationIndex];   

    events = [[Events alloc]    

               initWithDestinationIndex:destinationIndex];    

}

  return self;

}

As I mention in Chapter 12, Trip is a composite object that uses other objects to carry out its responsibilities. Whereas you put the Weather logic in the Trip object itself, in this case, you create a new model object to handle the events’ responsibilities. That’s because handling the events is a bit more complex and deserving of its own model object to encapsulate the logic. Hiding the Events object behind Trip makes things more loosely coupled — a very good thing, which you’ll find as you extend and enhance your app. (See Chapter 12 for an explanation of loose coupling.)

Adding the Events Class

If Trip is to use an Events object, you had better create the class. Follow these steps:

1. In the Project navigator, select the Model Classes group and then either right-click the selection and choose New File from the menu that appears or choose FileNewNew File from the main menu (or press Command Key+N).

Whatever method you choose, you’re greeted by the New File dialog.

2. In the left column of the dialog, select Cocoa Touch under the iOS heading, select the Objective-C Class template in the top-right pane, and then click Next.

You’ll see a dialog that will enable you to choose the options for your file.

3. Enter Events in the Class field.

4. Choose or enter NSObject in the Subclass Of field then click Next.

The iPad and With XIB for User Interface check boxes are dimmed because they are not relevant here — Events is derived from NSObject, and not from any type of view controller.

5. In the Save sheet that appears, click Create.

The Events class is the model object that manages the events. Earlier, I said that I’m creating this model object to encapsulate the event logic, and although doing so may seem to be an overreaction here given that the logic isn’t that complex, I mainly want to show you how to do that. And in reality, you can imagine that the Events class could be expanded to do a lot more — such as return the location, process events from multiple sources, or even allow a user to add her own events.

To start adding the Events class, add the bolded code in Listing 16-6 to Events.h.

Listing 16-6: Updating the Events Interface

@interface Events : NSObject

- (id)initWithDestinationIndex:

                             (NSUInteger)destinationIndex;

- (NSUInteger)numberOfEvents;

- (NSString *)getEvent:(NSUInteger)index;

@end

This code has three methods: an initialization method and two methods to process the Trip requests.

Next, you need to add an instance variable. Add the code in bold in Listing 16-7 to Events.m.

Listing 16-7: Updating the Events Implementation

#import “Events.h”

@interface Events() {

  NSMutableArray *events;

}

@end

@implementation Events

As you can see, Listing 16-6 has an initialization method (which is used by Trip when it creates the Events object). Add the code in Listing 16-8 to Events.m to implement the initWithDestinationIndex: initialization method.

Listing 16-8 Initializing the Events Object

- (id)initWithDestinationIndex:

                            (NSUInteger)destinationIndex {

self = [super init];

  if (self) {

    NSString *filePath = [[NSBundle mainBundle]

        pathForResource:@”Destinations” ofType:@”plist”];  

    NSDictionary *destinations = [NSDictionary

                 dictionaryWithContentsOfFile: filePath];

    NSArray *destinationsArray =

          destinations[@”DestinationData”];

NSDictionary *data =

       destinationsArray[destinationIndex];

    events = [NSMutableArray arrayWithArray:

                           data[@”Events”]];   

  }

  return self;

}

All this method does at this point is get the array of URLs for the HTML pages I created and you entered in the Destinations plist. It puts these URLs in an array that you create — for more efficient retrieval later. (I make this a mutable array because in the future you may want to allow a user to add his own events.)

The EventsController, as you will see, will need to know the number of events and the event information. You’ve added the methods to Trip in Listings 16-3 and 16-4, but Trip will actually be getting that information from Events. Add the code in Listing 16-9 to Events.m to implement the method that returns the number of events.

Listing 16-9: The Number of Events

- (NSUInteger)numberOfEvents {

  return [events count];

}

To get the number of events, you return the count of the array.

The EventsController will also need to have a list of the event URLs. Add the code in Listing 16-10 to Events.m to implement that method.

Listing 16-10: Getting an Event

- (NSString *)getEvent:(NSUInteger)index {

  return events[index];

}

To return an Event, you return the URL based on the index into the array. This will make more sense when you go through the EventsController and EventPageController code, which you do next.

The EventsController and Its PageViewController

At the start of this chapter, I promised to show you how to enable users to turn the page between one view controller and another. To implement this cool page-turning stuff, you need a UIPageViewController. You create that in the EventsController in its viewDidLoad method.

To start, though, you need to make the EventsController a UIPageViewController data source and delegate. (Actually, in this implementation, you won’t need to use any of the delegate methods, but it’s good for you to know about them.) Add the bolded code in Listing 16-11 (which includes the declaration of another method that you’ll use shortly) to EventsController.h.

Listing 16-11: Updating the EventsController Interface

#import “RTDetailViewController.h”

@class EventPageController;

@interface EventsController : RTDetailViewController

                <UIPageViewControllerDelegate,  

                          UIPageViewControllerDataSource>

- (EventPageController *)viewControllerAtIndex:

  (NSUInteger)index storyboard:(UIStoryboard *)storyboard;

@end

Now read on to find out about the data source and delegate.

Data sources and delegates

You’ve used delegates a few times already, such as when you add the code to the app delegate in Chapter 8. A data source is really just another kind of delegate that supplies the data that a framework object needs. In Chapter 20, when you implement a dynamic Table view, you do that as well, and data sources are also used in many other places in the framework — in picker views, for example, when you select a time or date in the Calendar application.

Data source

The UIPageViewController is a new Container View controller for creating page-turn transitions between view controllers first implemented in iOS 5. This means that for every page, you create a new view controller.

The UIPageViewControllerDataSource protocol is adopted by an object that provides view controllers (you’ll be using the PageDataController) to the Page View controller as needed, in response to navigation gestures.

UIPageViewControllerDataSource has two required methods:

image pageViewController:viewControllerAfterViewController: returns the view controller after the current view controller.

image pageViewController:viewControllerBeforeViewController: returns the view controller before the current view controller.

Delegate

The delegate of a Page View controller must adopt the UIPageViewControllerDelegate protocol. The methods in this protocol allow you to receive a notification when the device orientation changes or when the user navigates to a new page. In the implementation in this book, you don’t need to be concerned with either of those two situations.

The EventsController

Before you add any code, update EventsController.m with the bolded code in Listing 16-12.

Listing 16-12: Updating the EventsController Implementation

#import “EventsController.h”

#import “RTAppDelegate.h”

#import “Trip.h”

#import “EventPageController.h”

@interface EventsController () {

  NSUInteger pageCount;

  UIPageViewController *pageViewController;

  NSUInteger currentPage;

}

@end

@implementation EventsController

The viewDidLoad method is where most of the work is done. Add the bolded code in Listing 16-13 to viewDidLoad in EventsController.m.

Listing 16-13: Updating viewDidLoad

- (void)viewDidLoad

{

  [super viewDidLoad];

  self.navigationController.navigationBar.tintColor =   

                                     [UIColor blackColor];

  RTAppDelegate *delegate =

            [[UIApplication sharedApplication] delegate];

  pageCount = [delegate.trip numberOfEvents];

  pageViewController = [[UIPageViewController alloc]

    initWithTransitionStyle:

              UIPageViewControllerTransitionStylePageCurl

    navigationOrientation:

      UIPageViewControllerNavigationOrientationHorizontal

    options:nil];

  pageViewController.dataSource = self;

//pageViewController.delegate = self;

  EventPageController *startingViewController =

    [self viewControllerAtIndex:0

                              storyboard:self.storyboard];

  NSArray *viewControllers = @[startingViewController];

[pageViewController setViewControllers:viewControllers

     direction:

            UIPageViewControllerNavigationDirectionForward

     animated:NO completion:NULL];

  [self addChildViewController:pageViewController];

  [pageViewController didMoveToParentViewController:self];    

  [self.view addSubview:pageViewController.view];

  self.view.gestureRecognizers =

                    pageViewController.gestureRecognizers;

}

In the bolded code added here, you start out by setting the Navigation bar color to black. (You could’ve also done this in Interface Builder in the Events Controller Attributes inspector, where you set the title and identifier.)

Next, you get the number of events from the Trip model so that you know how many pages you’ll have:

  RTAppDelegate *delegate = [[UIApplication

                             sharedApplication] delegate];

  pageCount = [delegate.trip numberOfEvents];

Then you allocate and initialize the PageViewController and make yourself the data source. I have commented out the delegate assignment because you aren’t implementing any of the delegate methods, but here’s where you would do it:

pageViewController = [[UIPageViewController alloc]

  initWithTransitionStyle:

              UIPageViewControllerTransitionStylePageCurl

  navigationOrientation:

       UIPageViewControllerNavigationOrientationHorizontal        

       options:nil];

  pageViewController.dataSource = self;

//pageViewController.delegate = self

You’re using a UIPageViewControllerTransitionStylePageCurl (which gives the appearance of turning a page) and you use a Navigation orientation of horizontal, which gives you left-to-right page turning (UIPageViewControllerNavigationOrientationVertical gives you pages turning up and down.)

You then request the first view controller (I show this method in Listing 16-14 and explain it there), create an array, add the first view controller to the array, and pass that array to the pageViewController:

EventPageController *startingViewController = [self

      viewControllerAtIndex:0 storyboard:self.storyboard];

NSArray *viewControllers =

         [@startingViewController];

[pageViewController setViewControllers:viewControllers

  direction:UIPageViewControllerNavigationDirectionForward

  animated:NO completion:NULL];

This array will hold the view controllers that the UIPageController manages. You specify the direction as Forward. You set animated to NO for this transition (setting the view controller array, not the page turning) and you specify no completion block.

Although this approach is pretty simple, you can get way more sophisticated and include features such as double pages and even two-sided pages, and so on. You won’t be doing that here.

Next, you add the pageViewController as a child view controller, inform the pageViewController that it’s now the child of another view controller, and make its view a subview so that it’s displayed. The idea behind a Container View controller (which the UIPageViewContainer andUINavigationController both are) is that it manages the presentation of the content from its child view controllers (contained view controllers).

[self addChildViewController:pageViewController];

[pageViewController didMoveToParentViewController:self];

[self.view addSubview:pageViewController.view];

Finally, add the Page View controller’s gesture recognizers to the EventsController view so that the gestures are started further up the chain. (I explain gesture recognizers in Chapter 13.)

self.view.gestureRecognizers =

                   pageViewController.gestureRecognizers;

As a supplier of view controllers, you’ll be responsible for creating, managing, and returning the right view controller for a page. You’ll do that in the viewControllerAtIndex:storyboard: method.

Add the viewControllerAtIndex:storyboard: method in Listing 16-14 to EventsController.m.

imageYou’ll see some Live Issues errors until you add the page property in the next section.

Listing 16-14: Adding the viewControllerAtIndex:storyboard: Method

- (EventPageController *)viewControllerAtIndex:

    (NSUInteger)index

                  storyboard:(UIStoryboard *)storyboard {   

  if ((pageCount == 0) || (index >= pageCount)) {

    return nil;

  }

  EventPageController *eventPageController = [storyboard

    instantiateViewControllerWithIdentifier:@”EventPage”];

  eventPageController.page = index;

  return eventPageController;

}

With Listing 16-14, you’re simply doing some error checking to make sure that both pages are available and that the page for the view controller you’re supposed to return is available:

  if ((pageCount == 0) || (index >= pageCount)) {

    return nil;

  }

You then allocate and initialize the view controller for that page, setting its page (relative number of the URL to display) so that it knows which event URL to load:

  EventPageController *eventPageController = [storyboard

    instantiateViewControllerWithIdentifier:@”EventPage”];

  eventPageController.page = index;

imageYou have more efficient ways to do this. You could create a cache of controllers that you’ve created and reuse them as needed. (As you’ll see in Chapter 19, that’s how I do it with Table View cells.) I’ll leave you to explore that topic on your own.

You also need to add the required data source methods in Listing 16-15 to EventsController.m.

Listing 16-15: Implementing pageViewController:viewControllerAfterViewController: and pageViewController:viewControllerBeforeViewController:

- (UIViewController *)pageViewController:

               (UIPageViewController *)pageViewController

                  viewControllerBeforeViewController:

                      (UIViewController *)viewController {

    NSUInteger index =

            ((EventPageController *)viewController).page;

    if (index == 0)

      return nil;

    index--;

    currentPage = index;

return [self viewControllerAtIndex:index

                   storyboard:viewController.storyboard];

  }

- (UIViewController *)pageViewController:

               (UIPageViewController *)pageViewController

                  viewControllerAfterViewController:

                      (UIViewController *)viewController {

  NSUInteger index =

             ((EventPageController *)viewController).page;

  index++;

  if (index == pageCount)

    return nil;

  currentPage = index;

return [self viewControllerAtIndex:index  

                    storyboard:viewController.storyboard];

  }

Both of these methods return an EventDisplayController initialized with the right page (relative event number) to display. They use the viewControllerAtIndex:storyboard: method that you add in Listing 16-14 and indicate which view controller is required by taking the current view controller’s page number and then either incrementing or decrementing it appropriately. It then does some error checking to be sure that the page requested is within bounds. If it’s not, the method returns nil, and the UIPageViewController inhibits the page turn.

These data source methods are used by the UIPageViewController to get the view controllers that can display the next or previous page, depending on the way the user is turning the page. As mentioned previously, the UIPageViewController just manages controllers and the transitions between them. The view controllers that you return operate like the run-of-the-mill view controller you’ve been using, such as the Weather controller that displays a website.

The next section gives you a look at how these controllers work.

The EventPageController

The EventPageController is almost identical to the WeatherController that you implemented in Chapter 15.

To follow along in this section, you need to close the Assistant, display the Project navigator, and select EventPageController.m.

I have you add the same functionality to this controller as you did to the WeatherController so that you can select a link and navigate back from it. You could’ve created an abstract class — a WebViewController, for example — that both WeatherController and EventPageController were derived from, but because the EventPageController is contained by the UIPageViewController, having an abstract class gets to be a bit more complex.

The EventPageController is what actually displays the event and works exactly the same as the WeatherController.

First, add the page property, which is set by the viewControllerAtIndex:storyboard: method, by adding the bolded code to the EventPageController.h interface in Listing 16-16.

Listing 16-16: Updating the EventPageController Interface

#import <UIKit/UIKit.h>

@interface EventPageController : UIViewController

                                      <UIWebViewDelegate>

@property (readwrite, nonatomic) NSUInteger page;

@end

Here’s where you make the page (number) a property to enable you to determine which URL to load. You also have the EventPageController adopt the UIWebViewDelegate protocol.

Add the bolded code to the EventPageController.m implementation in Listing 16-17.

Listing 16-17: Updating the EventPageController Implementation

#import “EventPageController.h”

#import “RTAppDelegate.h”

#import “Trip.h”

@interface EventPageController ()

@property (weak, nonatomic) IBOutlet UIWebView *eventDataView;

@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;

@end

@implementation EventPageController

All the work is done in viewDidLoad and the other methods you add. These methods are the same as the code and methods you added to create the WeatherController. If you are hazy on what each does, refer to Chapter 15.

Add the bolded code in Listing 16-18 to EventPageController.m.

Listing 16-18: Updating viewDidLoad

- (void)viewDidLoad

{

  [super viewDidLoad];

  self.eventDataView.delegate = self;

  [self.activityIndicator startAnimating];

  self.activityIndicator.hidesWhenStopped = YES;

  [self.eventDataView setScalesPageToFit:YES];

  RTAppDelegate *appDelegate =

             [[UIApplication sharedApplication] delegate];

  [self.eventDataView loadRequest:

    [NSURLRequest requestWithURL:

          [NSURL URLWithString:

                 [appDelegate.trip getEvent:self.page]]]];

}

Next, add the rest of the Web View Delegate methods in Listing 16-19, just as you do in Chapter 15.

Listing 16-19: Implementing webView:shouldStartLoadWithRequest: navigationType:, webViewDidFinishLoad:, and add goBack

- (BOOL)webView:(UIWebView *)webView

    shouldStartLoadWithRequest:(NSURLRequest *)request

  navigationType:(UIWebViewNavigationType)navigationType {

  if (navigationType ==

                     UIWebViewNavigationTypeLinkClicked) {   

    UIBarButtonItem *backButton = [[UIBarButtonItem alloc]                                      

      initWithTitle:[NSString stringWithFormat:

        @”Back to %@”, self.parentViewController.

                               parentViewController.title]

      style:UIBarButtonItemStylePlain target:self

                               action:@selector(goBack:)];

    self.parentViewController.parentViewController.

           navigationItem.rightBarButtonItem = backButton;

    return YES;

  }

  else return YES;

}

- (void)goBack:(id)sender {

  [self.eventDataView goBack];

}

- (void)webViewDidFinishLoad:(UIWebView *)webView {

  [self.activityIndicator stopAnimating];

  if ([self.eventDataView canGoBack] == NO ) {

    self.parentViewController.parentViewController.

                  navigationItem.rightBarButtonItem = nil;

  }

}

This code enables you to click a link in an event and then return to the original event page. This is similar to the WeatherController functionality, but here you’re adding a button to the Navigation bar rather than to a toolbar.

And Then There’s the Bar Button Item

If you build and run the app now, all is well and good except for one thing — because you’re now using a Navigation controller, the text color of the Road Trip button is yellow. (If you remember, you set the default in application:DidFinishLaunchingWithOptions: in Chapter 8.)

Not to worry. In RTDetailViewController splitViewController:willHideViewController:withBarButtonItem: forPopoverController:, you just need to add one line of code to make the Road Trip button text green. Add the code in bold in Listing 16-20 tosplitViewController:willHideViewController:withBarButtonItem:forPopoverController: in RTDetailViewController.m.

Listing 16-20: Keeping the Road Trip Button Color Green

- (void)splitViewController:

                 (UISplitViewController *)splitController willHideViewController:

                        (UIViewController *)viewController withBarButtonItem:

                          (UIBarButtonItem *)barButtonItem forPopoverController:

                 (UIPopoverController *)popoverController

{

  barButtonItem.title =

            NSLocalizedString(@”Road Trip”, @”Road Trip”);

  [barButtonItem setTitleTextAttributes:

   [NSDictionary dictionaryWithObject:[UIColor greenColor] forKey:UITextAttributeTextColor] forState:UIControlStateNormal];

  if ([[self.splitViewController.viewControllers lastObject] isKindOfClass:[UINavigationController class]])

    [self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];

  else {

    NSMutableArray *itemsArray =

                         [self.toolbar.items mutableCopy];

    [itemsArray insertObject:barButtonItem atIndex:0];

    [self.toolbar setItems:itemsArray animated:YES];

  }

  self.masterPopoverController = popoverController;

  self.popOverButton = barButtonItem;

}

Adding Events Support to the iPhone Storyboard

The Events-related parts of storyboard file for the iPhone is simpler than the one you just did for the iPad, because you don’t start with a Navigation controller. The necessary components for the iPhone are shown in Figure 16-3. This figure includes both a Push segue and an Embed segue.

image

Figure 16-3: Components used in the iPhone storyboard file for the Events feature.

Your Objective-C code doesn’t have to change at all. The key components for the Events feature on the iPhone only require that you add components to the iPhone Storyboard file, connect them to the appropriate Objective-C classes, and connect them to work together. Your small task is summarized in the following. See Figure 16-3 for a diagram of how these components fit together.

image You must add a new Events controller to the iPhone Storyboard. This Events controller must be set to use the custom EventsController class.

image The “Events” Table view cell in the RTMasterViewController’s Table view must be connected with a push segue to the new Events controller.

image An instance of UIPageViewController must be embedded in the EventsController container. This UIPageViewController is responsible for managing transitions (such as page curls) between events pages.

image A custom Event Page controller must be added to the iPhone Storyboard. It is responsible for displaying event information in an HTML web page rendered by an instance of UIWebView. This Event Page controller must be set to use the custom EventPageController class.

You can assemble this the same way that you added the Events-related component to your iPad storyboard earlier in this chapter, except that you can skip the Navigation controller and connect a Push segue directly to your Events controller scene.