How’s the Weather? Working with Web Views - Adding the App Content - iOS App Development For Dummies (2014)

iOS App Development For Dummies (2014)

Part V. Adding the App Content

image

image Visit www.dummies.com/extras/iosappdevelopment for more features you can add to your iOS app.

In this part …

· Working with web views

· Geocoding

· Finding a location

· Selecting a destination

Chapter 15. How’s the Weather? Working with Web Views

In This Chapter

arrow Having your app actually deliver content

arrow Displaying a web page

arrow Understanding the ins and outs of Web views

arrow Encountering some interesting runtime errors

Getting the framework (no pun intended) in place for a new app is certainly a crucial part of the development process, but in the grand scheme of things, it’s only the spadework that prepares the way for the really cool stuff. After all is said and done, you still need to add the content that the users see or interact with. Content (and functionality) is, after all, the reason they will buy the app.

Now that you have created the storyboard scenes by specifying the view controllers and have spiffed up the Master View controller, it’s time to make those new view controllers earn their keep. As I've explained more than once, view controllers are the key elements here. They’re the ones that get the content from the Trip model object and send it to the view to display. In this chapter, you create a view controller that lets the Weather view know where to get the weather information it needs.


What is content? What is functionality?

Hundreds of pages into this book, it may seem a little late to be asking about content and functionality, but it’s not. Up to this moment, you’ve seen the basics of how to put an app together — how to design an interface with storyboards, how to use built-in classes and how to create new classes based on them, and how to work with animation, sound, and navigation. Although content and functionality are the two most basic issues you deal with in starting to build an app, until now, you’ve needed to become familiar with the tools and features of iOS and Cocoa Touch.

What is the purpose of your app? Is it to show off your development skills just to prove that you can do it, or are you deliberately building a portfolio that can lead to gainful employment in the app world? Have you been tasked with building an app for a specific purpose or client? And is the app supposed to make money?

Don’t let the technology get in the way of your content and functionality. Users don’t really care about what you need to worry about with view controllers and classes. They want to use your app and make it their own. At the 2013 Worldwide Developers Conference, speaker after speaker stressed that the purpose of the major revisions to the interface in iOS 7 was to get the interface out of the way and to focus on the content and functionality of your apps. To paraphrase a saying from the political world, “It’s the content, stupid.”

But to present the content simply and clearly, you need to do your hard work of coding and designing the interface. So now it’s back to that part of the story.


The Plan

You will be adding a custom view controller for displaying the weather, which is actually pretty easy. As is always the case, the view controller will be interacting with a view (actually a hierarchy of views) in a storyboard. There’s one view controller and two storyboards — one for iPad and one for iPhone.

The iPad storyboard

The Weather-related part of the iPad storyboard is shown in Figure 15-1.

The control flow through the iPad storyboard goes like this:

1. The iPad user interface is controlled by an instance of Apple’s UISplitViewController, which manages a Master View controller and a Detail View controller.

2. The Master View controller, displayed on the left in Landscape orientation, has a relationship to a UINavigationController. This relationship is shown as an object in the storyboard file.

3. The Detail View controller, which is always displayed, has a relationship with the custom WeatherController that you’ll develop in this chapter.

4. You need a navigation controller as a wrapper around the Table view that is displayed in the Master View controller (there is another navigation controller used with the Events controller).

5. The first navigation controller has a relationship to your custom MasterViewController.

6. The MasterViewController manages the Table view that you provide to let the user decide what should be displayed in the Detail view.

7. You’ll create a Replace segue to connect the first (Weather) item in the Table view to your weather scene.

8. You’ll use a UIWebView to display the contents of a web page inside your custom WeatherController. Voilà — the user can see a weather forecast.

The iPhone storyboard

The Weather-related part of the iPhone storyboard is shown in Figure 15-2.

image

Figure 15-1: Weather-related part of the iPad storyboard.

image

Figure 15-2: Weather-related part of the iPhone storyboard.

The control flow through the iPhone storyboard goes like this:

1. The iPhone user interface is controlled by an instance of Apple’s UINavigationController. The Navigation controller is needed to allow the user to pop (for example, return from) any view controller that is pushed using the Push segue in Step 4.

2. Your UINavigationController has a relationship to your Master View controller.

3. The MasterViewController manages the Table view that you are using to let the user decide what should be displayed when a table element is selected.

4. You’ll connect the first item in your MasterViewController to your WeatherController using a Push segue.

5. You’ll use a UIWebView to display the contents of a web page inside your custom WeatherController. Voilà — the user can see a weather forecast.

Setting Up the Weather Controller

If the user selects Weather from the Master view in the RoadTrip app, he comes face-to-face with an Internet site displaying weather information. (You’ll start with the URL specified in the Destination.plist, but you can use any site that you’d like.)

In this section, you add the initial Objective-C code for WeatherController class, and then add the logic it needs to get the right URL for the weather from the Trip object and send it on to the Weather (Web) view to load.

You’ll use the same WeatherController class in both the iPad and iPhone storyboard files. Add the WeatherController to the iPad storyboard first. After that, you can use the same class in the iPhone storyboard.

Adding the custom view controller

Although you have a view controller defined in the storyboard, it's a generic view controller — in this case, a UIViewController — and it's clueless about what you want to display in a view, much less the model it will need to get the data from. In this section, you create a custom controller that does know about its view and the model. Replace the generic controller with a custom one. Follow these steps:

1. In the Project navigator, select the ViewController Classes group and then right-click and choose New File from the contextual menu that appears.

Or choose File⇒New⇒File from the main menu (or press image+N).

Whatever method you choose, the New File dialog appears.

2. In the left column of the New File 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. In the Class field of the dialog, enter WeatherController, choose or enter DetailViewController in the Subclass Of field, make sure that the Target for iPad check box is selected and that the With XIB for User Interface is deselected, and then click Next.

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

You’ve got yourself a new a custom view controller.

Setting Up WeatherController in the Main_iPad.storyboard file

Adding a new custom view controller is a good start, but you still need to tell the storyboard that you want it to load the new 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 – Weather in the View Controller – Weather Scene.

The Weather View controller is selected on the canvas.

2. image Open the Utility area and then click the Identity Inspector icon in the Inspector selector bar to open the Identity inspector in the Utility area. Choose WeatherController from the Class drop-down menu (replacing UIViewController) in the Custom Class section, as I have in Figure 15-3.

Doing so means that, when Weather is selected in the Master View controller, WeatherController will now be instantiated and initialized and will receive events from the user and connect the view to the Trip model.

image

Figure 15-3: Now the storyboard Weather controller is connected to Weather Controller.

3. Drag in a Web view from the Utility area’s Library pane and position it to fill the Weather controller's view, as shown in Figure 15-4.

In iOS 7, part of the focus on content includes using the full screen. Make certain that the toolbar is translucent so that the Web view can be seen dimly through it when the app runs.

For the RoadTrip app, you want to use a UIWebView to display the weather information. This makes sense because you’ll be using a website to display the weather.

image As I explain in Chapter 4, the UIWebView class provides a way to display HTML content. These views can be used as the Main view, or as a subview of another view; wherever they’re used, they can access websites.

image

Figure 15-4: Adding the Web view.

4. image With the Web view selected, use the Editor menu to pin it to the superview by choosing

Editor⇒Pin⇒Leading Space to Superview

Editor⇒Pin⇒Trailing Space to Superview

Editor⇒Pin⇒Top Space to Superview

Editor⇒Pin⇒Bottom Space to Superview

You need to set up two outlets: one to the Web view so that WeatherController can tell the Web view what website to load and a second one to the toolbar so it can place the button there.

5. image Close the Utility area and select the Assistant from the Editor selector in the Xcode toolbar.

image

Figure 15-5: Displaying the correct file in the Assistant.

6. If theWeatherController.h file isn't the one that's displayed in the Assistant editor, go up to the Assistant’s Jump bar and select it, as I have done in Figure 15-5.

image

Figure 15-6: Make the Weather
Controller a Web view delegate.

7. Control-drag from the Web view (either on the Canvas or in the Document Outline) to theWeatherController interface and create anIBOutlet named weatherView.

image

Figure 15-7: Connecting the toolbar to the Detail View Controller base class.

8. Control-drag from the Web view in the storyboard Canvas (or in the Document Outline) to theWeatherController object in the Document Outline and then choose Delegate (see Figure 15-6) from the Outlets menu that appears.

I’m showing you this to illustrate that you can do all this dragging either on the Canvas or in the Document Outline.

image You must set WeatherController to be a delegate of the view, but you can do so either using code or using the storyboard steps described here. You’ll do it in code in the next chapter, so you can take your choice. If, like me, you have a tendency to forget to connect the delegate, you may want to pick one technique to do consistently. (I connect delegates in storyboards as soon as I create the object that’s going to be the delegate.)

You still need to connect the toolbar to the DetailViewController, the WeatherController’s superclass. You take care of that in the next step.

9. image Select the Standard editor in the Editor selector on the toolbar, select Weather Controller in the Document Outline, and open the Connections inspector using the Inspector selector, as I have in Figure 15-7.

You could also right-click or Control-click WeatherController in the Document Outline to get a similar menu.

10. Drag from the toolbar Outlet in the Connections inspector to the toolbar in the Document Outline. (See Figure 15-7.)

You have to use the Document Outline because the Web view is on top of the toolbar on the canvas so you can’t see it. You didn’t need to create the toolbar Outlet because it had already been created along with UIViewController. The idea here is that you can use the same click-and-drag technique you used to create an outlet to modify which object an existing outlet connects to.

If you were to compile and run the RoadTrip project, you’d see the blank screen displayed at app launch. You could select Weather or Test Drive in the Master View controller (in Portrait or Landscape orientation, mind you), but you’d just see a blank screen if you selected Weather. You’ll fix that next.

But keep in mind this is a major step forward. You now have a fully functioning application structure that can, with equal aplomb, use a Navigation controller and its Navigation bar (as you’ll see in the next chapter), or simply replace the controller in the Detail view and use a toolbar.

At this point, you have the WeatherController class set up and you’ve arranged for the storyboard to create a UIWebView object and set all the outlets (the toolbar and weatherView) for you when the user selects Weather for the view he wants to see.

Because the Trip object owns the data — in this case, the data is provided by the website you’re using to display the weather information, but the information about the website is managed by the Trip object— you add the methods necessary to the Trip model to provide this toWeatherController.

image Select the Standard editor in the Editor selector on the toolbar and select Trip.h in the Project navigator. (If you managed to close the Project navigator at some point in your travels, select its icon in the View selector or choose View⇒Navigators⇒Show Project Navigator to open it again.)

Add the declaration for the weather method (the bolded code) to the Trip interface in Trip.h, as shown in Listing 15-1.

Listing 15-1: Updating the Trip.h Interface

#import <Foundation/Foundation.h>
#import <mapKit/MapKit.h>

@interface Trip : NSObject

- (id)initWithDestinationIndex:(int)destinationIndex;
- (UIImage *)destinationImage;
- (NSString *) destinationName;
- (CLLocationCoordinate2D) destinationCoordinate;
- (NSString *)weather;

@end

Add the weather method in Listing 15-2 to Trip.m.

Listing 15-2: Adding the weather Method

- (NSString *)weather {

return _destinationData[@"Weather"];
}

All the Trip object does here is return the URL that the Web view will use to download the weather HTML page for the site. It got the URL for that site from the dictionary you create in Chapter 11 when you load the Destinationplist that provides the data for this destination.

The Weather Controller

Now that you have the Trip object set up to deliver the data you need, the WeatherController needs to pass on to the view to load the web page.

You need to add some #import compiler directives so that Weather Controller can access AppDelegate to get the Trip reference and request the data it needs.

To do that, add the bolded code in Listing 15-3 to WeatherController.m.

Listing 15-3: Updating the WeatherController Implementation

#import "DetailViewController.h"
#import "AppDelegate.h"
#import "Trip.h"

You're going to need a Back button so add this line to the class extension at the top of WeatherController.m.

@property (strong, nonatomic) UIBarButtonItem *backButton;

The template provides a viewDidLoad method stub when you create the controller file. You may recall from previous chapters that this is where you want to have the Web view (or any other view) load its data. Add the bolded code in Listing 15-4.

Listing 15-4: Adding to viewDidLoad

- (void)viewDidLoad
{
[super viewDidLoad];
self.title = @"Weather";
self.weatherView.scalesPageToFit = YES;
AppDelegate *appDelegate =
[[UIApplication sharedApplication] delegate];
self.backButton = [[UIBarButtonItem alloc]
initWithTitle:[NSString stringWithFormat:
@"Back to %@", self.title]
style:UIBarButtonItemStyleBordered
target:self
action:@selector(goBack:)]; [self.weatherView
loadRequest: [NSURLRequest requestWithURL:
[NSURL URLWithString:
[appDelegate.trip weather]]]];
}

The first thing you do in Listing 15-4 is set the title to Weather.

The title won’t be showing up on the toolbar, however, because this just sets the title in the Navigation bar. I’ll use it when I add a Return to Whatever button in the next section.

Because what gets loaded is going to be a website, you set self.weatherView.scalesPageToFit to YES.

scalesPageToFit is a UIWebView property. If it’s set to YES, the web page is scaled to fit inside your view, and the user can zoom in and out. If it’s set to NO, the page is displayed in the view, and zooming is disabled.

I might set it to NO when I’m not displaying a web page and the HTML page I created fits just fine and I don’t want it to be scalable. You may want to do something else here, of course; I did it this way to show you how (and where) you have control of web page properties.

Next, you create the Back button that will be used to navigate the Web view. As you'll see, you will alternately add and remove this button from the toolbar or navigation bar when there is or isn't a page to go back to. You create the Back to Whatever button using the view controller title, and return YES to tell the Web view to load from the Internet. The action:@selector(goBack:) argument is the standard way to specify Target-Action. It says that when the button is tapped, you need to send the goBack: message to the target:self, which is the WeatherController.

UIBarButtonItem *backButton = [[UIBarButtonItem alloc]
initWithTitle:[NSString stringWithFormat:
@"Back to %@", self.title]
style:UIBarButtonItemStylePlain target:self
action:@selector(goBack:)];
self.navigationItem.rightBarButtonItem = backButton;
return YES;

image You may want to adjust the title depending on whether you are on an iPad or iPhone. Because there is more space on an iPad, you may want to go with the title suggested here (Back to <title>). On iPhone, you may want to just use the Back chevron and the title or name.

You then create the NSURLRequest object that the Web view needs to load the data. To do that, you first create an NSURL object (an object that includes the utilities necessary for downloading files or other resources from web and FTP servers) using the URL you get from Trip. The code uses this NSURL and creates an NSURLRequest from it. The NSURLRequest is what the WeatherController needs to send to the Web view in the loadRequest: message, which tells it to load the data associated with that particular NSURL.

image The NSURLRequest class encapsulates a URL and any protocol-specific properties, all the time keeping things protocol-independent. It also provides a number of other things that are beyond the scope of this book but are part of the URL loading system — the set of classes and protocols that provide the underlying capability for an app to access the data specified by a URL. This is the preferred way to access files both locally and on the Internet.

The loadRequest message is sent to the Web view, and the Weather website is displayed in the window. This causes the Web view to load the data and display it in the window.

Managing links in a Web view

An interesting thing about the Weather view — or any other view that does (or can) load real web content into your app instead of using a browser — is that the links are live and users can follow those links from that view if you let them.

After the user is at the weather website, as you can see in Figure 15-8, the user might want to look at the NWS New York, NY link in the upper-left corner. If the user were to follow that link, though, he wouldn’t have a way to get back to the originating page.

Hmm.

To be able to navigate back to the originating view, you need to create another button and label it Back to Weather (or whatever the previous controller is) so that the user knows that she can use it to get back to the previous view. Creating this button is pretty easy to do, as you’ll see in Listing 15-6.

image

Figure 15-8: You can select a link (left) to look at National Weather Service info for New York (right) — but you have no way to get back to the originating view.

Of course, I don’t want to have that button pop up if the user is at the originating web page because, at that point, there’s no going back. So, how do you keep track of who’s where in the whole navigational link structure? Here, you're assisted by two Web view delegate methods,webView:shouldStartLoadWithRequest:navigationType: and webViewDidFinishLoad:.

webView:shouldStartLoadWithRequest:navigationType: is a UIWebView delegate method. It’s called before a Web view begins loading content to see whether the user wants the load to proceed.

First, adopt the UIWebViewDelegate protocol by adding the bolded code in Listing 15-5 to WeatherController.h.

Listing 15-5: Updating the WeatherController Interface

#import "DetailViewController.h"

@interface WeatherController : DetailViewController
<UIWebViewDelegate>

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

@end

Remember that when you adopt a delegate protocol, the compiler will then check to make sure that all required methods are in fact there and that all types are correct — so do it!

Next, add the code in Listing 15-6 to WeatherController.m.

Listing 15-6: Implementing the webView:shouldStartLoadWithRequest:navigationType: Method

- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:
(NSURLRequest *)request navigationType:
(UIWebViewNavigationType)navigationType {

if (navigationType ==
UIWebViewNavigationTypeLinkClicked){

if ([[UIDevice currentDevice]
userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
if (![self.toolbar.items
containsObject:self.backButton])
{ NSMutableArray *itemsArray =
[self.toolbar.items mutableCopy];

[itemsArray addObject:self.backButton ];
[self.toolbar setItems:itemsArray animated:NO];
}
else {
self.navigationItem.rightBarButtonItem =
self.backButton;
}
}
return YES;
}

Early on in Listing 15-6, you check to see whether the user has touched an embedded link. (You have to see whether a link is clicked because this message is sent to the delegate under several different circumstances.)

if (navigationType == UIWebViewNavigationTypeLinkClicked){

Then, you check to see if you're on an iPad or not. If you're on an iPad, you'll be using a toolbar, but on an iPhone, you'll use a navigation bar.

if ([[UIDevice currentDevice]
userInterfaceIdiom] == UIUserInterfaceIdiomPad) {

If you're on an iPad and the user has clicked a link, you want to have a Back button (the one you created in viewDidLoad) so that the user can get back. Note that I said "have" a Back button and not "add" a Back button. If there's one there, you don't want to add a second. So you look at the items array in the toolbar to see if self.backButton is already there.

if (![self.toolbar.items
containsObject: self.backButton])

If there isn't a Back button there, add it with the standard code for doing this: you copy the toolbar's items array into a mutable array called itemsArray. Then you add the Back button to it and replace the toolbar's items array with the mutable array.

{
NSMutableArray *itemsArray = [self.toolbar.items
mutableCopy];
[itemsArray addObject:self.backButton ];
[self.toolbar setItems:itemsArray animated:NO];
}

Next, add the goBack: method in Listing 15-7 to the WeatherController.m file. This is the message sent when the Back to Whatever button is tapped.

Listing 15-7: Adding the goBack: Method

- (void)goBack:(id)sender {
[self.weatherView goBack];
}

image Note that you don’t need to declare this method in WeatherController.h because it's used within WeatherController.m for the target-action code in Listings 15-6 and 15-7. The UIWebView actually implements much of the behavior you need here. The Web view keeps a Backwardand Forward list. When you send the UIWebView the message (goBack:), it reloads the previous page.

Finally, you want to get rid of the Back to Whatever button when you’re displaying the original page. The code to do that is shown in Listing 15-8.

Listing 15-8: Implementing webViewDidFinishLoad:

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

if ([self.weatherView canGoBack] == NO ) {
NSUInteger backButtonIndex = [self.toolbar.items
indexOfObject: self.backButton];

if (([[UIDevice currentDevice] userInterfaceIdiom]
== UIUserInterfaceIdiomPad) &&
(backButtonIndex != NSNotFound)
{
NSMutableArray *itemsArray =
[self.toolbar.items mutableCopy];
[itemsArray removeObject:self.backButton ];
[self.toolbar setItems:itemsArray animated:NO];

}
else {
self.navigationItem.rightBarButtonItem = nil;
}
}
}

The delegate is sent the webViewDidFinishLoad: message after the view has loaded. At this point, you check to see whether there’s anything to go back to (the Web view keeps track of those sorts of things). If not, remove the button from the toolbar or Navigation bar.

image That being said, the Apple Human Interface Guidelines say it’s best to avoid creating an app that looks and behaves like a mini web browser. As far as I’m concerned, making it possible to select links in a Web view doesn’t do that. The Back button comes close. The choice is up to you.

But if you really don’t want to enable the user to follow links (either because of Apple’s suggestion that you not make your app act as a mini-browser or if you’d just prefer that your app users stick around for a bit and don’t go gallivanting around the Internet), you have to disable the links that are available in the content. You can do that in the shouldStartLoadWithRequest: method in the WeatherController.m file by replacing the code you added in Listing 15-6 with the code shown in Listing 15-9.

Listing 15-9: Disabling Links

- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {

if (navigationType ==
UIWebViewNavigationTypeLinkClicked){
return NO;
}
else return YES;
}

More Opportunities to Use the Debugger

A couple of runtime errors are easy to get. Two that pop up frequently are unrecognized selector sent to instance and NSUnknownKeyException. Although the former error is pretty easy to track down if you actually read the error message, the latter can be a real mystery (it was to me), especially the first time you encounter it. So I want to explain both of them now.

Unrecognized selector sent to instance

The unrecognized selector sent to instance runtime error is probably the most common one I get e-mails about; it (understandably) throws many people for a loop. But if you take time to read the error message, you can make sense of it.

2013-12-07 19:34:07.166 RoadTrip[1202:12503] ***
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[WeatherController
goBack]: unrecognized selector sent to instance 0xb7331f0'

This error occurs when you thought you created a selector in your code but it’s not (really) there.

If, in the webView shouldStartLoadWithRequest:navigationType: method, you mistakenly typed goBack (without a colon, designating a method with no arguments) rather than goBack: (a method with a single argument) when you allocated and initialized the backButton:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc]
initWithTitle:[NSString stringWithFormat:
@"Back to %@", self.title]
style:UIBarButtonItemStylePlain target:self
action:@selector(goBack)];

and then you ran the app, selected Weather, selected a link, and then tapped the Back to Weather button, what you will see in the Debugger Console pane is

2013-12-07 19:34:07.166 RoadTrip[1202:12503] ***
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[WeatherController
goBack]: unrecognized selector sent to instance 0xb7331f0'

You get this error message because you’re sending the goBack message (not the goBack: message) to the target — the WeatherController. The WeatherController does have a fully functional goBack: method implemented, but it doesn't have a goBack method implemented — as the debugger so clearly informs you.

Repeat for the iPhone Storyboard

The good news is that you can use the same custom WeatherController class in your iPhone storyboard as you used in the iPad storyboard file. You can also reuse the changes you made to the Trip model. You can update the iPhone storyboard file the same way that you did in Chapter 12.

Adding the WeatherController to the iPhone storyboard file

The following steps show how to update the iPhone storyboard file.

1. Select theMainStoryboard_iPhone.storyboard file in the Project Navigator.

2. Select the genericUIViewController scene in the Library and drag it onto your storyboard.

3. Open the Utility area and select the Identity inspector.

4. Change the class name toWeatherController in the Class drop-down menu, just as you did in Figure 15-3.

5. With the WeatherController scene selected in the storyboard file, select the view in the document outline.

6. Change the class name of the view fromUIView toUIWebView.

7. In the Document Outline, Control-drag from theWeatherController to the Web view and select weatherView from the pop-up menu.

This step connects the weatherView outlet in the WeatherController class to the instance of UIWebView that you’ll use to display the weather.

Test in the iPhone Simulator

Run the iPhone app in the Simulator. You should see the web page displayed in Figure 15-9.

image

Figure 15-9: The WeatherController display on the iPhone.