Chapter 8
Using Web Views

WHAT YOU LEARN IN THIS CHAPTER:

 
  • Displaying web pages in an app
  • Calling Core Foundation methods for efficient string manipulation
  • Using toolbars and buttons to create a lightweight web browser

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

You can find the wrox.com code downloads for this chapter at www.wrox.com/go/begiosprogramming on the Download Code tab. The code is in the chapter 08 download and individually named according to the names throughout the chapter.

One of the first and biggest selling points of smartphones was the ability to surf the web. Many refer to this as having the entire web in your pocket. Even before smartphones were the norm, some flip phones also allowed you to search the web with your cellular connection.

iOS devices come with Apple’s Mobile Safari web browser, but similar to sending e-mails and posting to social networking, it’s a better experience for users to view web pages without leaving the app they’re in. One of the feature requirements for the Bands app gives users the ability to search the web for information about a particular band. As the developer you could accomplish this by simply jumping the user out of the app and into Safari. Though easy to do, it breaks the flow of the user in your app. A better approach is to allow the user to surf the web within the app itself. In this chapter, you add a lightweight web browser to the bands apps, allowing users to search the web for their bands without leaving the app.

LEARNING ABOUT WEB VIEWS

In the Bands app you want to give the user the ability to search the web for information about a particular band. To do this you need a way to view web pages. UIKit has a user interface object called UIWebView that gives you this ability.

UIWebView is an HTML rendering UIKit object that does not have all the features that Mobile Safari does, which is by design because you can use UIWebView for many different things. Loading web pages via a URL is one of them, but you can also use it to display static HTML strings or to preview known file types such as PDFs, Word documents, and even Excel spreadsheets. UIWebView has no address bar or any user interface elements to perform any kind of navigation. (Though there are methods you can use to add them.) This gives developers the ability to use UIWebView just like any other user interface subview in things such as in a UITableViewCell or as the main part of a storyboard scene.

In the Bands app, you use a UIWebView in its own scene built out with navigation buttons to create an in-app browser. In the following Try It Out, you create a new scene for the UIWebView in the Storyboard and a manual push segue to navigate to it from the Band Details scene.

TRY IT OUT: Adding a UIWebView
 
  1. Select File ⇒ New ⇒ File from the Xcode menu, and add a new Objective-C class named WBAWebViewController with its parent class set to UIViewController.
  2. Select the WBAWebViewController.h class from the Project Navigator.
  3. Add an IBOutlet for a UIWebView using the following code:
    #import <UIKit/UIKit.h>

    @interface WBAWebViewController : UIViewController

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

    @end
  4. Select the Main.storyboard from the Project Navigator.
  5. Add a new View Controller from the Object library to the Storyboard.
  6. Select the new View Controller in the Storyboard hierarchy.
  7. In the Identify Inspector set its class to the WBAWebViewController you created in step 1.
  8. Select the Band Details scene, and Control-drag from the View Controller in the dock to the new View Controller, as shown in Figure 8-1, and create a Push segue to the new scene.
    阅读 ‧ 电子书库
    FIGURE 8-1
  9. Select the segue arrow, and set its identifier to webViewSegue in the Attributes Inspector.
  10. Select the UINavigationItem in the WBAWebViewController and set its title to Web Search in the Attributes Inspector.
  11. Drag a new Web View from the Object library onto the Web View scene, and set its frame to the entire UIView, as shown in Figure 8-2.
    阅读 ‧ 电子书库
    FIGURE 8-2
  12. Connect the UIWebView to the webView property of the WBAWebViewController class.
  13. Select the WBABandDetailsViewController.h class from the Project Navigator.
  14. Add a new constant to the WBAActivityButtonIndex enumeration using the following code:
    typedef enum {
    //    WBAActivityButtonIndexEmail,
    //    WBAActivityButtonIndexMessage,
        WBAActivityButtonIndexShare,
        WBAActivityButtonIndexWebSearch,
    } WBAActivityButtonIndex;
  15. Select the WBABandDetailsViewController.m class from the Project Navigator.
  16. Modify the activityButtonTouched: method with the following code:
    - (IBAction)activityButtonTouched:(id)sender
    {
        UIActionSheet *activityActionSheet = nil;
       
        /*
         if([MFMessageComposeViewController canSendText])
         activityActionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self
    cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Email",
    @"Message", nil];
         else
         activityActionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self
    cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Email",
    nil];
         */
       
        activityActionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self
    cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Share",
    @"Search the Web", nil];
       
        activityActionSheet.tag = WBAActionSheetTagActivity;
        [activityActionSheet showInView:self.view];
    }
  17. Modify the actionSheet:clickedButtonAtIndex: with the following code:
    - (void)actionSheet:(UIActionSheet *)actionSheet
         clickedButtonAtIndex:(NSInteger)buttonIndex
    {
        if(actionSheet.tag == WBAActionSheetTagActivity)
        {
            /*
             if(buttonIndex == WBAActivityButtonIndexEmail)
             {
             [self emailBandInfo];
             }
             else if (buttonIndex == WBAActivityButtonIndexMessage)
             {
             [self messageBandInfo];
             }
             */
           
            if(buttonIndex == WBAActivityButtonIndexShare)
            {
                [self shareBandInfo];
            }
            else if (buttonIndex == WBAActivityButtonIndexWebSearch)
            {
                [self performSegueWithIdentifier:@"webViewSegue" sender:nil];
            }
        }
        else if(actionSheet.tag == WBAActionSheetTagChooseImagePickerSource)
        {
            if(buttonIndex == WBAImagePickerSourceCamera)
            {
                [self presentCameraImagePicker];
            }
            else if (buttonIndex == WBAImagePickerSourcePhotoLibrary)
            {
                [self presentPhotoLibraryImagePicker];
            }
        }
        else if(actionSheet.tag == WBAActionSheetTagDeleteBandImage)
        {
            if(buttonIndex == actionSheet.destructiveButtonIndex)
            {
                self.bandObject.bandImage = nil;
                self.bandImageView.image = nil;
                self.addPhotoLabel.hidden = NO;
            }
        }
        else if (actionSheet.tag == WBAActionSheetTagDeleteBand)
        {
            if(actionSheet.destructiveButtonIndex == buttonIndex)
            {
                self.bandObject = nil;
                self.saveBand = NO;
               
                if(self.navigationController)
                    [self.navigationController popViewControllerAnimated:YES];
                else
                    [self dismissViewControllerAnimated:YES completion:nil];
            }
        }
    }


  18. Run the app in the iPhone 4-inch simulator. When you select the Search Web option from the activities, you should see the new Web View, as shown in Figure 8-3.
    阅读 ‧ 电子书库
    FIGURE 8-3
How It Works
The first thing you did was to create a new subclass of the UIViewController and name it WBAWebViewController. You then added a new UIWebView property named webView.
In the Storyboard, you added a new scene and set its identity to the WBAWebViewController class. This is the new Web View scene. You then created a manual segue from the Band Details scene to the Web View scene and set the segue identifier to webViewSegue. You set the identifier of this segue in order to initiate the segue in code.
The segue added a UINavigationItem to the Web View scene, giving you the ability to set its title. Finally, in the Storyboard you added the UIWebView to the Web View scene and connected it to the webView property in the WBAWebViewController class. You may have noticed that part of the UIWebView lies underneath the UINavigationItem. The UIWebView can detect this and make sure that the tops of pages load underneath the UINavigationItem without you needing to add any extra code.
In the WBABandDetailsViewController class you added a new option to the UIActivitySheet shown from the activity UIBarButtonItem and named it Search the Web. In the actionSheet:clickedButtonAtIndex: method you added a check for the WBAActivityButtonIndexWebSearch button index. If found, the code initiates the segue between the Band Details scene and the Web View scene using the performSegueWithIdentifier:sender: method of the UIViewController class (remember that WBABandDetailsViewController is a subclass of UIViewController, so you can use self to call this method) with the webViewSegue identifier you set in the storyboard.

Loading a URL

The app can now show a UIWebView, but it’s not interesting without a URL to load. To implement the search feature, you could load a site such as Google or Bing and have the user type the name of the band into the search box and go from there. A user-friendly approach is to build a URL that does the search right away without the user needing to type anything. The simplest search URL to build is from Yahoo. To search for a band, you simply add it to the end of the query string.

Loading the URL in a UIWebView is a little bit more involved. UIWebView does not have a method that takes a string and loads it as a URL. Instead, it has a loadRequest: method that takes an NSURLRequest. The NSURLRequest object is designed to handle any protocol; though for the Bands app, you use only HTTP. NSURLRequest, like the UIWebView, also does not have an initialization method that takes a simple string but instead takes an NSURL. The NSURL class enables you to manipulate the various aspects of a URL such as the host, port, and query string. It does have an initialization method that takes a string. The string needs to be well formed for NSURL to parse it. If it fails to parse, the initialization method returns nil.

In the bands app you create the Yahoo search URL as a string with the band name in the query string. This means you need to have the band name in the WBAWebViewController. To accomplish this, you need to implement the prepareForSegue:sender: method again, as you did in Chapter 5, “Using Table Views.”

TRY IT OUT: Loading a URL
 
  1. Select the WBAWebViewController.h file from the Project Navigator, and add the following property to the interface:
    #import <UIKit/UIKit.h>

    @interface WBAWebViewController : UIViewController

    @property (nonatomic, weak) IBOutlet UIWebView *webView;
    @property (nonatomic, strong) NSString *bandName;

    @end
  2. Select the WBAWebViewController.m file from the Project Navigator, and add the following viewDidAppear: method:
    - (void)viewDidAppear:(BOOL)animated
    {
        [super viewDidAppear:animated];
       
        NSString *yahooSearchString = [NSString
    stringWithFormat:@"http://search.yahoo.com/search?p=%@", self.bandName];
        NSURL *yahooSearchUrl = [NSURL URLWithString:yahooSearchString];
        NSURLRequest *yahooSearchUrlRequest = [NSURLRequest
    requestWithURL:yahooSearchUrl];
       
        [self.webView loadRequest:yahooSearchUrlRequest];
    }
  3. Select the WBABandDetailsViewController.m file from the Project Navigator.
  4. Import the WBAWebViewController.h with the following code:
    #import "WBAWebViewController.h"
  5. Add the following prepareForSegue:sender: method:
    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if([segue.destinationViewController class] == [WBAWebViewController class])
        {
            WBAWebViewController *webViewController = segue.destinationViewController;
            WBAWebViewController.bandName = self.bandObject.name;
        }
    }
  6. Run the app in the iPhone 4-inch simulator. When you select the Search the Web option, the web view displays a Yahoo search using the band name, as shown in Figure 8-4.
    阅读 ‧ 电子书库
    FIGURE 8-4
How It Works
First, you added a bandName property to the WBAWebViewController. Then in the implementation you added the UIViewControllerDelegate method viewWillAppear:. In that method you first create an NSString for the Yahoo search URL. Using that NSString you initialized a new NSURL instance, which you then used to initialize an NSURLRequest instance. The last step you added was a call to the loadRequest: method of the UIWebView using the NSURLRequest.
You also added the prepareForSegue:sender: method to the WBABandDetailsViewController class. In its implementation you use the class static method, which is part of the NSObject class. You can use this method to test if one object is the same type as another or if one object is an instance of a class. In this code you check to see if the destinationViewController is an instance of the WBAWebViewController class. If it is, you set the bandName property of the WBAWebViewController to the name property of the bandObject prior to the WBAWebViewController being pushed onto the navigation stack.

Loading a URL That Contains Special Characters

If you have a band with a space in its name, you can notice that only the first word is searched. This is because spaces are not allowed in URLs, along with a handful of other characters such as ampersands, question marks, and exclamation points. To have those characters in the query string, they need to be URL-encoded, which means replacing them with a percent character followed by the hexadecimal value for the character in ASCII.

String manipulation can be coded in a straightforward way, but it also requires a lot of CPU time. This can be both slow and power draining in a mobile app, which has limited CPU power and memory. Some languages are better at string manipulation than others. The C language is low level, which makes writing these types of methods faster and more efficient. Because it is also the base language of Objective-C, you can write C methods into apps.

Learning C is not an easy task. Writing complex string methods is even more difficult. Fortunately for iOS developers Apple has done the heavy lifting and made those methods available in the Core Foundation Framework. Core Foundation is written entirely in C but can be called from Objective-C. Core Foundation has a string method called CFURLCreateStringByAddingPercentEscapes that can scan a string for a set of characters and replace them with their percent character/hex value. You can use this method in the Bands app to properly encode the band name before adding it to the query string of the URL.

Calling the method is simple but Core Foundation uses different data types than Objective-C. For example, NSString in Core Foundation is CFStringRef. These data types are not ARC-compliant. ARC, as explained in Chapter 2, “Introduction to Objective-C,” stands for Automatic Reference Counting. It moves the burden of memory management from the developer to the compiler. The Core Foundation and Objective-C objects can be used interchangeably, but they need to be bridged. Apple has provided a solution for this called toll-free bridging. Essentially it’s a macro that transfers the ownership Core Foundation objects to ARC. In the Bands app, you call the CFBridgeRelease macro that transfers total control of the CFStringRef returned by the CFURLCreateStringByAddingPercentEscapes method back to an NSString controlled by ARC.

WHY USING THE NEW NSURLCOMPONENTS CLASS IS NOT ALWAYS THE BEST OPTION
iOS 7 includes a new class called NSURLComponents that you can use to build a properly encoded URL in most situations. As you set the various components of a URL, the class compares the characters and encodes any that are not part of the allowed characters. The reason you cannot use NSURLComponents with the band name is because ampersands are a valid character for the query string component of a URL and part of the URLQueryAllowedCharacterSet used to encode invalid characters. If there is a band with the name “this & that,” the URL created with NSURLComponents would be http://search.yahoo.com/search?p=this%20&%20that, which is incorrect. The correct URL is http://search.yahoo.com/search?p=this%20%26%20that. Using the CFURLCreateStringByAddingPercentEscapes function will escape the band name properly.
TRY IT OUT: URL Encoding a String
 
  1. Select the WBAWebViewController.m file from the Project Navigator.
  2. Modify the viewDidAppear: method with the following code:
    - (void)viewDidAppear:(BOOL)animated
    {
        [super viewDidAppear:animated];
       
        NSString *urlEncodedBandName = (NSString *)
    CFBridgingRelease(
    CFURLCreateStringByAddingPercentEscapes(NULL,(CFStringRef)self.bandName, NULL,
    (CFStringRef)@"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8 ));
        NSString *yahooSearchString = [NSString
    stringWithFormat:@"http://search.yahoo.com/search?p=%@", urlEncodedBandName];
        NSURL *yahooSearchUrl = [NSURL URLWithString:yahooSearchString];
        NSURLRequest *yahooSearchUrlRequest = [NSURLRequest
    requestWithURL:yahooSearchUrl];
       
        [self.webView loadRequest:yahooSearchUrlRequest];


    }
  3. Run the app in the iPhone 4-inch simulator. Searching the web for a band with an ampersand or other special characters now works.
How It Works
Prior to adding the band name to the query string, the bandName is URL encoded using the Core Foundation method CFURLCreateStringByAddingPercentEscapes. For example, if the bandName is “this & that,” the urlEncodedBandName is “this%20%26%20that” with the spaces being replaced with %20 and the ampersand replaced with %26.
The syntax for Core Foundation functions is C instead of Objective-C. This function takes five parameters. The first is the allocator, which you do not need so you pass in NULL. The second is the originalString to be fixed. The third is a CFStringRef with the characters to leave alone. If the bandName had characters already escaped, you would list them in this parameter. Because it doesn’t, you can again pass in NULL. The fourth parameter is the list of characters that should be escaped. For this you pass in all the characters that are not valid in a query string or which are part of building a query string, such as ampersands and question marks. The last parameter is the character encoding. The code passes in the kCFStringEncodingUTF8 constant because UTF8 is the correct encoding for URLs.
The code also uses casts to both cast the CFStringRef result back to an NSString as well as cast the NSString bandName and the NSString of characters to escape to CFStringRefs. The last thing this code does is use the toll-free bridging macro CFBridgingRelease to make the result compatible with ARC.

Showing User Feedback

When making a network connection and transferring data, it is important to let users know that this activity is taking place. iOS uses the Network Activity Indicator to give this feedback. The Network Activity Indicator is the tiny spinning icon in the status bar that you see when you surf around in Mobile Safari or check your e-mail in Mail. It’s available in your apps through the shared UIApplication object. You need to show this indicator in the Bands app (or any app that makes any type of network connection, for that matter) while the search is performed and the page loads. If you don’t, your app may be rejected by Apple.

For a UIWebView you need to implement three methods in the UIWebViewDelegate protocol to achieve this. As a web page loads, it does so with a series of requests. These requests happen asynchronously, so there can be any number of them loading at the same time. In your app, you want the Network Activity Indicator visible from the time the first request starts to when the last request finishes. The easiest way to keep track of this is to use a counter. When a request starts to load, the webViewDidStartLoad: method is called in the delegate. The webViewDidFinishLoad: method is called when a request finishes loading. Your code should increment and decrement the counter when these methods are invoked. When the count gets back to 0, your app knows that all the requests that have started have now completed, and the Network Activity Indicator can be hidden. Some requests may fail. When a request fails, the webViewDidFinishLoad: method is not called. If you don’t handle these failures, the load count never gets back to 0, and the Network Activity Indicator remains visible indefinitely. To handle this you also need to implement the webView:didFailLoadWithError: method to decrement the load count.

TRY IT OUT: Showing the Network Activity Indicator
 
  1. Select the WBAWebViewController.h file from the Project Navigator.
  2. Declare that the class implements the UIWebViewDelegate using the following code:
    @interface WBAWebViewController : UIViewController <UIWebViewDelegate>

    @property (nonatomic, weak) IBOutlet UIWebView *webView;
    @property (nonatomic, strong) NSString *bandName;

    @end
  3. Add the following property and method declaration to the interface:
    @interface WBAWebViewController : UIViewController <UIWebViewDelegate>

    @property (nonatomic, weak) IBOutlet UIWebView *webView;
    @property (nonatomic, strong) NSString *bandName;
    @property (nonatomic, assign) int webViewLoadCount;

    - (void)webViewLoadComplete;


    @end
  4. Select the Main.storyboard and connect the delegate of the UIWebView to the WBAWebViewController.
  5. Select the WBAWebViewController.m file from the Project Navigator.
  6. Modify the viewDidLoad method with the following code:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        self.webViewLoadCount = 0;
    }
  7. Add the following UIWebViewDelegate methods to the implementation:
    - (void)webViewDidStartLoad:(UIWebView *)webView
    {
        self.webViewLoadCount++;
        [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    }

    - (void)webViewDidFinishLoad:(UIWebView *)webView
    {
        self.webViewLoadCount--;
       
        if(self.webViewLoadCount == 0)
            [self webViewLoadComplete];
    }

    - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
    {
        self.webViewLoadCount--;
       
        if(self.webViewLoadCount == 0)
            [self webViewLoadComplete];
    }
  8. Add the webViewLoadComplete method to the implementation:
    - (void)webViewLoadComplete
    {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    }
  9. Run the app in the iPhone 4-inch simulator. As the web page loads, you can now see the Network Activity Indicator visible in the status bar.
How It Works
In the WBAWebViewController interface file, you declared that it implements the UIWebViewDelegate protocol and then added a property named webViewLoadCount to keep track of the load count. You also declared the webViewLoadComplete method. Then in the Storyboard you connected the UIWebView delegate to the WBAWebViewController.
In the WBAWebViewController implementation you initialized the webViewLoadCount to zero in the viewDidLoad method. Next, you implemented three methods of the UIWebViewDelegate protocol. In the webViewDidStartLoad: method you incremented the webViewLoadCount and set the Network Activity Indicator to be visible using the networkActivityIndicatorVisible property of the sharedApplication. In the webViewDidFinishLoad: method you decremented the webViewLoadCount and checked to see if it’s back to 0. If it is, you call the webViewLoadComplete method that sets the Network Activity Indicator back to being hidden. The webView:didFailLoadWithError: method does the same thing as the webViewDidFinishLoad method, making sure that the Network Activity Indicator is hidden when all the requests either finish or fail.

ADDING NAVIGATION

The Bands app can now search the web and show the results in the UIWebView. When users click a search result link, they can go to that page, but they have no way of getting back to the search results to look at another. Web browsers give you navigation buttons that enable you to go back to the previous page or go forward to a page you just visited. They typically have a backward navigation stack where the current page URL is added prior to loading the linked page. When a user goes back, the current URL is added to a forward navigation stack before being removed from the backward stack and reloaded. Though the UIWebView does not give you a user interface to navigate these stacks, it does keep track of them for you and gives you methods to know if they have items as well as methods to perform the navigation. To add navigation to the Bands app, you need to build your own user interface. You do that using a UIToolbar.

Creating a Toolbar

A UIToolbar is similar to the UINavigationItem you’ve been using in previous chapters. It takes the entire width of the screen and enables you to add UIBarButtonItems. Unlike the UINavigationItem, the UIBarButtonItems do not have a set place. By default they are left-aligned with no spacing between them. To get UIBarButtonItems arranged in a UIToolbar with proper spacing, you use either fixed space or flexible space UIBarButtonItems. These are special implementations of UIBarButtonItem that do not enable user interaction and appear as blank space. Fixed-space UIBarButtonItems have a set width you can set. Flexible-space UIBarButtonItems can expand to the right, taking as much space as they can before encountering another UIBarButtonItem. For example, if you have two regular UIBarButtonItems with a single flexible UIBarButtonItem in between them, you will have one button on the left of the toolbar and the other all the way on the right.

UIBarButtonItems in a UIToolbar work the same as they do in the UINavigationItem. You can connect them with IBOutlets in your code to do things such as setting whether they are enabled. You can also connect them to IBActions so that they actually do something when touched.

In the Bands app you can add a UIToolbar that enables the user to navigate forward and back as well as stop a page from loading or reload the page after it has been loaded. To start, you first get the UIToolbar and UIBarButtonItems added to the scene and connected to IBOutlets and IBActions.

TRY IT OUT: Adding a Toolbar and Buttons
 
  1. Select the WBAWebViewController.h file from the Project Navigator.
  2. Add the following IBOutlets to the interface:
    @property (nonatomic, weak) IBOutlet UIBarButtonItem *backButton;
    @property (nonatomic, weak) IBOutlet UIBarButtonItem *stopButton;
    @property (nonatomic, weak) IBOutlet UIBarButtonItem *refreshButton;
    @property (nonatomic, weak) IBOutlet UIBarButtonItem *forwardButton;
  3. Add the following IBActions to the interface:
    - (IBAction)backButtonTouched:(id)sender;
    - (IBAction)stopButtonTouched:(id)sender;
    - (IBAction)refreshButtonTouched:(id)sender;
    - (IBAction)forwardButtonTouched:(id)sender;
  4. Select the WBAWebViewController.m file from the Project Navigator, and add the following methods to the implementation:
    - (IBAction)backButtonTouched:(id)sender
    {
        NSLog(@"backButtonTouched");
    }

    - (IBAction)stopButtonTouched:(id)sender
    {
        NSLog(@"stopButtonTouched");
    }

    - (IBAction)refreshButtonTouched:(id)sender
    {
        NSLog(@"refreshButtonTouched");
    }

    - (IBAction)forwardButtonTouched:(id)sender
    {
        NSLog(@"forwardButtonTouched");
    }
  5. Select the Main.storyboard from the Project Navigator.
  6. Drag a Toolbar from the Object library onto the bottom of the Web View scene.
  7. Adjust the UIWebView so that the bottom aligns with the top of the UIToolbar, as shown in Figure 8-5.
    阅读 ‧ 电子书库
    FIGURE 8-5
  8. Select the default UIBarButtonItem on the UIToolbar, and set its Identifier to Rewind in the Attribute Inspector.
  9. Drag a new Bar Button Item from the Object library to the UIToolbar and set its Identifier to Stop in the Attribute Inspector.
  10. Drag a new Bar Button Item from the Object library to the UIToolbar and set its Identifier to Refresh.
  11. Drag one more Bar Button Item from the Object library to the UIToolbar, and set its Identifier to Fast Forward.
  12. Drag a Flexible Space Bar Button Item from the Object library, and place it in between the Rewind and Stop buttons.
  13. Drag another Flexible Space Bar Button Item from the Object library, and place it between the Stop and Refresh buttons.
  14. Drag one more Flexible Space Bar Button Item from the Object library, and place it between the Refresh and Fast Forward buttons, as shown in Figure 8-6.
    阅读 ‧ 电子书库
    FIGURE 8-6
  15. Connect the buttons with their appropriate IBOutlets and IBActions.
  16. Run the app in the iPhone 4-inch simulator. You now see the UIToolbar with the UIBarButtonItems equally spaced, as shown in Figure 8-7.
    阅读 ‧ 电子书库
    FIGURE 8-7
How It Works
The first thing you did was to declare the IBOutlets for the four UIBarButtonItems you will be adding to the UIToolbar in the WBAWebViewController interface. Next, you declared the IBActions that will be called when the UIBarButtonItems are tapped. In the implementation you added simple implementations of each IBAction that write which one was called to the console.
You did the work for this Try It Out in the Storyboard. First, you added the actual UIToolbar to the Web View scene. Next, you adjusted the frame of the UIWebView to align to the top of the UIToolbar. This differs from the UINavigationItem. The UINavigationItem is designed to be semi-transparent, so when users scroll the page, they see a blurred representation under the UINavigationItem. UIToolbar is not designed to be transparent, so any part of the UIWebView that lies underneath it will never be visible to users. You may have noticed, though, that you did not need to add auto-layout constraints to either the UIWebView or the UIToolbar to keep them anchored to the bottom of the UIView. Those constraints are built in for you.
Next, you added the four UIBarButtonItems and set their identifiers to show an appropriate icon. You then added the three flexible-space UIBarButtonItems so that all the UIBarButtonItems are spaced equally across the UIToolbar. Finally, you connected the appropriate IBOutlets and IBActions to the UIBarButtonItems.

With the UIToolbar user interface in place, you can now add the calls for navigation. The UIBarButtonItems can add a little more user feedback so that users know when the page is loading, when it’s complete, and when they can navigate back and forth. To do this you disable UIBarButtonItems depending on what state the UIWebView is in.

The back and forward UIBarButtonItems should be enabled only when there is a URL on their respective stacks. The UIWebView has methods you can call to determine this. The canGoBack method returns true if there’s a URL on the back navigation stack. The canGoForward does the same for the forward navigation stack. You can use the isLoading property of the UIWebView to determine when the stop and reload UIBarButtonItems should be enabled. Stop should be enabled only when the isLoading property is true. The reload button is enabled only when the isLoading property is false.

UIWebView also gives you the goBack and goForward methods to load URLs off the navigation stacks. There is also the stopLoading method that stops the current page that is loading. The UIWebView has a request property that holds the initial NSURLRequest made to load the page. Reloading simply loads the UIWebView using the current request.

TRY IT OUT: Controlling the Web View
 
  1. Select the WBAWebViewController.h file from the Project Navigator, and add the following method declaration:
    - (void)setToolbarButtons;
  2. Select the WBAWebViewController.m file from the Project Navigator.
  3. Add the setToolbarButtons method to the implementation:
    - (void)setToolbarButtons
    {
        self.backButton.enabled = self.webView.canGoBack;
        self.forwardButton.enabled = self.webView.canGoForward;
        self.stopButton.enabled = self.webView.isLoading;
        self.refreshButton.enabled = !self.webView.isLoading;
    }
  4. Modify the webViewDidStartLoad: method with the following code:
    - (void)webViewDidStartLoad:(UIWebView *)webView
    {
        self.webViewLoadCount++;
        [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
       
    [self setToolbarButtons];
    }
  5. Modify the webViewLoadComplete: with the following code:
    - (void)webViewLoadComplete
    {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
       
    [self setToolbarButtons];
    }
  6. Modify the backButtonTouched: method with the following code:
    - (IBAction)backButtonTouched:(id)sender
    {
        NSLog(@"backButtonTouched");
       
    [self.webView goBack];
    }
  7. Modify the forwardButtonTouched: method with the following code:
    - (IBAction)forwardButtonTouched:(id)sender
    {
        NSLog(@"forwardButtonTouched");
       
    [self.webView goForward];
    }
  8. Modify the stopButtonTouched: method with the following code:
    - (IBAction)stopButtonTouched:(id)sender
    {
        NSLog(@"stopButtonTouched");
       
    [self.webView stopLoading];
        self.webViewLoadCount = 0;
        [self webViewLoadComplete];



    }
  9. Modify the refreshButtonTouched: method with the following code:
    - (IBAction)refreshButtonTouched:(id)sender
    {
        NSLog(@"refreshButtonTouched");
        [self.webView loadRequest:self.webView.request];
    }
  10. Run the app in the iPhone 4-inch simulator. You can now navigate back and forth through the UIWebView navigation stacks as well as stop and reload web pages.
How It Works
In the WBAWebViewController interface you declared the setToolbarButtons method. In the implementation you implemented the method so that the back button is enabled when canGoBack returns true, the forward button when canGoForward returns true, the stop button when the isLoading property is true, and the reload button when the isLoading property is false. Next, you modified the IBActions to call their corresponding methods in UIWebView. In the backButtonTouched: and forwardButtonTouched: methods you call the goBack and goForward methods of the UIWebView. The stopButtonTouched: method first calls the stopLoading method of the UIWebView. Next it resets the webViewLoadCount to 0, then calls the webViewLoadComplete method to reset the UIToolbar and hide the Network Activity Indicator. The reloadButtonTouched: method calls the same loadRequest: method you call in the viewDidAppear:, using the request property of the UIWebView.

Opening Safari

UIWebViews are a nice addition to an app, but they do not give users all the functionality they have in Mobile Safari. Apple gives third-party developers access to some of its native apps using URL schemes and the openURL method in the shared UIApplication. Opening a URL in Mobile Safari requires only passing the URL into openURL. You can remember from early in this chapter, though, that the UIWebView can load many URL requests while loading the page. To get the main URL, you again use the NSURLRequest stored in the request property of the UIWebView. It has a property named mainDocumentURL that holds the URL for the main page.

To add this feature to the Bands app, you implement another Action UIBarButtonItem in the UINavigationItem as you did in the Band Details scene. When tapped, it shows a UIActionSheet with Open in Safari and Cancel as options. This approach makes users aware that they are about to leave the app and gives them an option to cancel.

TRY IT OUT: Opening Safari
 
  1. Select the WBAWebViewController.h file from the Project Navigator.
  2. Add a new enumeration to track the UIActionSheet button indexes using the following code:
    typedef enum {
        WBAWebViewActionButtonIndexOpenInSafari,
    } WBAWebViewActionButtonIndex;
  3. Declare that the WBAWebViewController class implements the UIActionSheetDelegate with the following code:
    @interface WBAWebViewController : UIViewController <UIWebViewDelegate,
    UIActionSheetDelegate>
  4. Add the following IBAction to the interface:
    - (IBAction) webViewActionButtonTouched:(id)sender;
  5. Select the Main.storyboard file from the Project Navigator.
  6. Drag a new Bar Button Item from the Object library to the UINavigationItem of the WBAWebViewController and set its Identifier to Action.
  7. Connect the Action UIBarButtonItem to the webViewActionButtonTouched: method in the WBAWebViewController.
  8. Select the WBAWebViewController.m file and add the following methods to the implementation:
    - (IBAction)webViewActionButtonTouched:(id)sender
    {
        UIActionSheet *webViewActionSheet = [[UIActionSheet alloc]
    initWithTitle:nil delegate:self cancelButtonTitle:@"Cancel"
    destructiveButtonTitle:nil otherButtonTitles:@"Open in Safari", nil];
        [webViewActionSheet showInView:self.view];
    }

    - (void)actionSheet:(UIActionSheet *)actionSheet
    clickedButtonAtIndex:(NSInteger)buttonIndex
    {
        if(buttonIndex == WBAWebViewActionSheetButtonIndexOpenInSafari)
        {
            [[UIApplication sharedApplication]
    openURL:self.webView.request.mainDocumentURL];   
        }
    }
  9. Run the app in the iPhone 4-inch simulator. When you tap the Action button and select Open in Safari, the system switches from the Bands app to Mobile Safari and loads the current web page.
How It Works
First, you declared a new enumeration named WBAWebViewActionSheetButtonIndex with the value WBAWebViewActionSheetButtonIndexOpenInSafari to keep track of the UIActionSheet button indexes. Though it only has one value, it is best to continue using the same approach you used with other UIActionSheet button indexes. You do not need a UIActionSheet tag enumeration, though, since only one is shown in the WBAWebViewController. Next you declared that the WBAWebViewController implements the UIActivitySheetDelegate protocol as well as declaring a new IBAction. In the Storyboard you added a UIBarButtonItem to the UINavigationItem. You then set its identifier to Action to get the appropriate icon and connected it to its webViewActionButtonTouched: method in the WBAWebViewController.
In the WBAWebViewController implementation you added the webViewActionButtonTouched: method that displays the UIActionSheet with the Open in Safari option. In the actionSheet:clickedButtonAtIndex: method of the UIActionSheetDelegate protocol, you checked to see if the buttonIndex is equal to the WBAWebViewActionSheetButtonIndexOpenInSafari constant. If it was, you added code to call the openURL method of the shared UIApplication using the mainDocumentURL of the UIWebViews current request.

SUMMARY

Surfing the web is a popular feature of mobile devices. It enables users to search the web without needing a laptop or desktop computer. Adding the ability to search and load web content is also a popular feature in third-party apps. In this chapter, you added the web search feature to the Bands app by building a lightweight browser using the UIWebView and UIWebViewDelegate protocol, as well as a UIToolbar and UIBarButtonItems. You also learned how to call C-level Core Foundation methods to perform complex string manipulations in an efficient way and then pass the results back to Objective-C.

EXERCISES
 
  1. How do you trigger a manually created segue in code?
  2. Which framework can you use to call low-level C-language methods?
  3. How do you show the Network Activity Indicator?
  4. What UIWebViewDelegate protocol method gets called if a request fails to load?
  5. What method of the shared UIApplication object can you call to open Safari from your app?
WHAT YOU LEARNED IN THIS CHAPTER
TOPIC KEY CONCEPTS
UIWebView The UIWebView is the UIKit object used to render HTML or to preview well-known file types such as PDFs and Word documents. They can load a web page using a network connection or display an HTML string or a file included with the app.
Core Foundation Framework The Core Foundation Framework is a collection of functions written in C, which you can call from an Objective-C class. Because string manipulation is resource intensive, it is better to use Core Foundation Frameworks when performing complex tasks.
Network Activity Indicator When an app is making a network connection and sending or downloading data, it needs to let the user know this activity is taking place. All iOS devices include a spinning icon in the status bar called the Network Activity Indicator. The Bands app needs to show the Network Activity Indicator while the UIWebView is loading a request.
UIToolbar The UIToolbar UIKit object is how you implement a toolbar in an app. It has a set of UIBarButtonItems that act as both the visible buttons as well as the blank space separating buttons.
Open in Safari The shared UIApplication has a method named openURL that you can use to launch built in Apple apps such as Mobile Safari. When used in combination with a UIWebView, you give users the ability to open the web page they are viewing in your app directly in Mobile Safari.