Displaying Events Using a Page View Controller - Adding the App Content - iOS App Development For Dummies (2014)

iOS App Development For Dummies (2014)

Part V. Adding the App 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 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 pages” in the same way as 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 connect to the 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, 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.

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

· The “Events” Table view cell in the MasterViewController’s Table view.

· 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 MasterViewController.

· A custom EventsController container.

· 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.

· 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 WeatherController object. The way you develop a storyboard is rather formulaic. I review it:

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 File⇒New⇒File from the main menu (or press image+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 DetailViewController 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 File⇒New⇒File from the main menu (or press image+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

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 Main_iPad.storyboard 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.

2. image 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 theEventsController on the Canvas.

(You don’t have to put it there, but doing so hints that a relationship may exist; it also makes it easier to draw that relationship if you want to do so — and you will want to do so in a moment.)

2. image 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).

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

4. Add a Web view to theEventPageController by dragging in a Web view from the Library pane and into the Event Page controller.

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

The UIWebView class provides a way to display HTML content and has the built-in functionality to download HTML content from the web.

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

Set the X and Y origins to zero and 64 and then resize the Web view to fill the view. The standard for iOS 7 is that views should appear through a translucent navigation bar dimly (iOS 7 takes care of this for you). In this case, when the iPad split view controller is visible in the master view controller, a navigation bar is shown there (with the title), and I think it looks better to have that space visible in the detail view controller right next to it. Depending on what is behind the translucent bar, the visual effect varies. When it's a map that's scrollable, in many ways each part of the map is the same so placing it behind the translucent bar is fine (and suggested). When you're loading a web page as in this case, you may not know what is going to be seen (the format of the weather page is not under your control, for example), and in a case such as that, I prefer to place the web view lower down so it is not shown behind the navigation bar. That's what happens here: 64 = status bar (20) + navigation bar (44).

6. 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.

image As 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 when it comes to subviews.) If that’s not the case, rearrange the views in the Document Outline.

image

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

7. image Switch to the Size inspector in the Utility area using the Inspector selector, and then use Editor⇒Align⇒Horizontal Center in Container and Editor⇒Align⇒Vertical Center in Container to center the activity indicator.

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

9. Control-drag from the Web view in either the Canvas or the Document Outline to theEventPageController class extension and create anIBOutlet (just as you do in Chapter 15) namedeventDataView.

10. Control-drag from the Activity Indicator view to theEventPageController class extension at the top of the file and create anIBOutlet namedactivityIndicator.

11. 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 ()
@property (strong, nonatomic)
NSDictionary *destinationData;
@property (strong, nonatomic)
Destination *destination;
@property (strong, nonatomic)
Events * events;

@end

image Until 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 [self.events numberOfEvents];
}

Listing 16-4: Get an Event

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

return [self.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;
}

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 11 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 File⇒New⇒File from the main menu (or press image+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 and 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 a property. Add the code in bold in Listing 16-7 to Events.m to create a class extension with a property. (The basic class extension without the property may already be in your project.)

Listing 16-7: Updating the Events Implementation

#import "Events.h"

@interface Events ()
@property (strong, nonatomic) 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];
self.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 [self.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 self.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 "DetailViewController.h"
@class EventPageController;

@interface EventsController : DetailViewController
< 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. 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:

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

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

image Although it is easiest to use a separate page controller for each page, you can actually reuse page view controllers by replacing the data on the page view controller’s view with data for the new page. If you are building something along the lines of an ebook with perhaps hundreds of pages, that would be the way to go. With a limited number of pages such as is the case with RoadTrip, separate page view controllers work just fine.

Delegate

The delegate of a Page View controller must adopt the UIPageView
ControllerDelegate 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 "AppDelegate.h"
#import "Trip.h"
#import "EventPageController.h"

@interface EventsController ()
@property (strong, nonatomic)
UIPageViewController *pageViewController;
@property (nonatomic) NSUInteger pageCount;
@property (nonatomic) 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];

AppDelegate *delegate =
[[UIApplication sharedApplication] delegate];
self.pageCount = [delegate.trip numberOfEvents];
self.pageViewController = [[UIPageViewController alloc]
initWithTransitionStyle:
UIPageViewControllerTransitionStylePageCurl
navigationOrientation:
UIPageViewControllerNavigationOrientationHorizontal
options:nil];
self.pageViewController.dataSource = self;
EventPageController *startingViewController =
[self viewControllerAtIndex:0
storyboard:self.storyboard];
NSArray *viewControllers = @[startingViewController];
[self.pageViewControllsetViewControllers:viewControllers
direction:
UIPageViewControllerNavigationDirectionForward
animated:NO completion:NULL];
[self addChildViewController:self.pageViewController];
[self.pageViewController didMoveToParentViewController:self];
[self.view addSubview:self.pageViewController.view];
self.view.gestureRecognizers =

self.pageViewController.gestureRecognizers;
}

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

AppDelegate *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:

self.pageViewController = [[UIPageViewController alloc]
initWithTransitionStyle:
UIPageViewControllerTransitionStylePageCurl
navigationOrientation:
UIPageViewControllerNavigationOrientationHorizontal
options:nil];
self.pageViewController.dataSource = 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];
[self.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:self.pageViewController];
[self.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 farther up the chain. (I explain gesture recognizers in Chapter 13.)

self.view.gestureRecognizers =
self.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 view
ControllerAtIndex:storyboard: method.

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

image You’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 ((self.pageCount == 0) || (index >= self.pageCount)) {
return nil;
}
EventPageController *eventPageController = [storyboard
instantiateViewControllerWithIdentifier:@"Event Page"];
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 ((self.pageCount == 0) || (index >= self.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:@"Event Page"];
eventPageController.page = index;

image You 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:viewControllerAfterView Controller: and pageViewController:viewControllerBeforeViewController

- (UIViewController *)pageViewController:
(UIPageViewController *)pageViewController
viewControllerBeforeViewController:
(UIViewController *)viewController {
NSUInteger index =
((EventPageController *)viewController).page;
if (index == 0)
return nil;
index--;
self.currentPage = index;
return [self viewControllerAtIndex:index
storyboard:viewController.storyboard];
}

- (UIViewController *)pageViewController:
(UIPageViewController *)pageViewController
viewControllerAfterViewController:
(UIViewController *)viewController {
NSUInteger index =
((EventPageController *)viewController).page;
index++;
if (index == self.pageCount)
return nil;
self.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 view
ControllerAtIndex: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 "AppDelegate.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];
AppDelegate *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.

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.

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

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

· 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.

· 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.