Tutorial: Building a Web Browser with UIWebView (Part 2)

Web Browser Address and Title Ba

Figure 1: Web Browser Address and Title Bar

A new version of this tutorial, updated for Xcode 5 and iOS 7, is available here: Building a Web Browser with UIWebView Revisited (Part 2)

In yesterdays tutorial, Building a Web Browser with UIWebView (Part 1) you completed a skeletal implementation of a web browser. In today’s tutorial you will build upon that code to add a Safari-like address bar as shown in Figure 1.

If you completed the previous tutorial you can simply continue on with your existing code, or if you prefer you can download the starting point code there: WebBrowser1.zip.

To implement the address bar you are going to programmatically create a UINavigationBar and add a UITextField to allow the user to type in a URL and a UILabel to display the page title.

Defining Properties for the New UI Elements

Open WebBrowserViewController.h and add the highlighted code. (I have omitted some of the exisiting code in the file for brevity).

@interface WebBrowserViewController : UIViewController<UIWebViewDelegate> {

//...

    UIBarButtonItem* mStop;
    UILabel* mPageTitle;
    UITextField* mAddressField;
}

//...

@property (nonatomic, retain) IBOutlet UIBarButtonItem* stop;

@property (nonatomic, retain) UILabel* pageTitle;
@property (nonatomic, retain) UITextField* addressField;

//...

@end

Lines 6, 7, 14 and 15 define the instance variables and properties you will use to refer to the title label and address field. Notice that the properties are not marked IBOutlet this is because you will not be using them in Interface Builder.

Next add the synthesize code to WebBrowserViewController.m at the top of the @implementation section.

@implementation WebBrowserViewController
//...
@synthesize pageTitle = mPageTitle;
@synthesize addressField = mAddressField;

Since the properties are marked retain, before you go any further, you may as well get the dealloc and viewDidUnload out of the way.

- (void)dealloc
{
    //...

    [mPageTitle release];
    [mAddressField release];
    [super dealloc];
}
- (void)viewDidUnload
{
    //...

    self.pageTitle = nil;
    self.addressField = nil;
    [super viewDidUnload];
}

Creating the New Elements

When you are creating UI elements programmatically it can take quite a bit of fiddling with the coordinates and spacing to get the layout to look good. Luckily in this case I have down the fiddling for you. Copy and paste the following into WebBrowserViewController.h.

#import "WebBrowserViewController.h"

static const CGFloat kNavBarHeight = 52.0f;
static const CGFloat kLabelHeight = 14.0f;
static const CGFloat kMargin = 10.0f;
static const CGFloat kSpacer = 2.0f;
static const CGFloat kLabelFontSize = 12.0f;
static const CGFloat kAddressHeight = 26.0f;

I’ll rarely say copy and paste the code (even though I am sure many people do it), but there is not much to be learnt from the above code. It is simply defines some constants that result in a nice layout.

Now locate viewDidLoad begin with this modification. (Note: the next 5 code boxes are code you add one after the other to viewDidLoad)

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    CGRect navBarFrame = self.view.bounds;
    navBarFrame.size.height = kNavBarHeight;
    UINavigationBar *navBar = [[UINavigationBar alloc] initWithFrame:navBarFrame];
    navBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;

This code creates a UINavigationBar with the same width as the screen and with the height you defined at the top of the file. The autoresizing mask UIViewAutoresizingFlexibleWidth ensures that if the user rotates the device the navigation bar will expand or contract to fill the width of the screen.

Next you are going to add the title label to this navigation bar:

    CGRect labelFrame = CGRectMake(kMargin, kSpacer, 
                navBar.bounds.size.width - 2*kMargin, kLabelHeight);
    UILabel *label = [[UILabel alloc] initWithFrame:labelFrame];
    label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    label.backgroundColor = [UIColor clearColor];
    label.font = [UIFont systemFontOfSize:12];
    label.textAlignment = UITextAlignmentCenter;
    [navBar addSubview:label];
    self.pageTitle = label;
    [label release];

Lines 1-3 create the new UILabel. Lines 4-7 set various visual attributes. Line 8 adds the newly created label to the navigation bar. Line 9 initializes the property you created earlier on. From now on you can refer to the label as self.pageTitle. Since you are finished using label you release it on line 10.

    CGRect addressFrame = CGRectMake(kMargin, kSpacer*2.0 + kLabelHeight, 
                                     labelFrame.size.width, kAddressHeight);
    UITextField *address = [[UITextField alloc] initWithFrame:addressFrame];
    address.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    address.borderStyle = UITextBorderStyleRoundedRect;
    address.font = [UIFont systemFontOfSize:17];
    [navBar addSubview:address];
    self.addressField = address;
    [address release];

The code for creating the text field follows the same pattern as the label.

Next you need to add the navigation bar to the view controllers view (this is the view that contains all the elements you created in Interface Builder in the previous tutorial).

                
    [self.view addSubview:navBar];
    [navBar release];

Notice that once the variable navBar is released you no longer have a way of referring to the navigation bar. This is not a problem since you will not need to. It is worth mentioning that the reason the navigation bar does not disappear is because adding it as a subView causes it to be retained by the parent view.

Finally you need to adjust the frame of the web view to ensure that it is not partially covered by the navigation bar.

    CGRect webViewFrame = self.webView.frame;
    webViewFrame.origin.y = navBarFrame.origin.y + navBarFrame.size.height;
    webViewFrame.size.height = self.toolbar.frame.origin.y - webViewFrame.origin.y;
    self.webView.frame = webViewFrame;

Now you should be able to compile and run your project. At this point touching the address field will cause the on screen keyboard to appear, but clicking return will not cause the web view to load the entered address. The title bar also remains blank.

Making the Address Bar Work

To make the web view load the entered address you need to tell the address field what action it should take when the user finishes editing an address. You do this by setting an action for the UIControlEventEditingDidEndOnExit control event. Add the highlighted code into viewDidLoad.

    address.font = [UIFont systemFontOfSize:17];
    [address addTarget:self 
                action:@selector(loadAddress:event:) 
      forControlEvents:UIControlEventEditingDidEndOnExit];
    [navBar addSubview:address];

The @selector(...) syntax allows you to refer a method by name. So this line of code tells the address field to invoke the method loadAddress:event: on self which, in this case, is you view controller. You now need to implement this method.

You begin by adding the method declaration to WebBrowserViewController.h

- (void)loadAddress:(id)sender event:(UIEvent*)event;

and then the implementation in WebBrowserViewController.m

- (void)loadAddress:(id)sender event:(UIEvent *)event
{
    NSString* urlString = self.addressField.text;
    NSURL* url = [NSURL URLWithString:urlString];
    NSURLRequest* request = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:request];
}

The UIWebView class defines a method loadRequest: that takes an NSURLRequest object and starts the web view loading the new request. Line 3 retrieves the string from the address field. Line 4 uses the string to create a URL. Line 5 uses the URL to create an NSURLRequest. Finally, line 6 instructs the web view to load this new request.

Compile and run your project and confirm that you can successfully load another website. There are still some glitches with the address field. If you use the forward and back buttons the address field is out of sync with the displayed page. Tomorrow’s tutorial will address that problem.

Making the Title Bar Work

When a web page finishes loading you should update the title bar to reflect the title of the newly loaded page. Recall that UIWebView calls webViewDidFinishLoad: when a page finishes loading (or an error method). Add the following line of code to your webViewDidFinishLoad: implementation.

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    [self updateButtons];
    [self updateTitle:webView];
}

You have not defined updateTitle: yet so add the following declaration to WebBrowserViewController.h:

- (void)updateTitle:(UIWebView*)aWebView;

And now for the implementation… If you look at Apple’s documentation of UIWebView you’ll notice something odd; there is no method to get the title of the HTML document loaded in the web view. So how can we find out this information?

While there is not direct function to get the title of the page, there is a function that allows you to execute JavaScript. The JavaScript expression document.title can be used to get the page title. This leads to the following implementation:

- (void)updateTitle:(UIWebView*)aWebView
{
    NSString* pageTitle = [aWebView stringByEvaluatingJavaScriptFromString:@"document.title"];
    self.pageTitle.text = pageTitle; 
}

If you now compile and run your project the title of the page should appear when a page loads.

Remaining Issues

There are still a number of small issues that need to be taken care of. Tomorrow’s tutorial will address them. They are:

  • the address field can get out of sync with the displayed page,
  • the keyboard displayed is not the URL keyboard so it is awkward to type a URL,
  • the keyboard auto-capitalization mode is inappropriately set,
  • there is no clear button in the address field,
  • if the user omits the http:// the page will not load,
  • if an error occurs, the user is never informed.

Source Code Download

You can download the completed source code for this part of the project here WebBrowser2.zip

This web site is ad supported. If you would like to see other great tutorials like this one please visit one of our sponsors.


Posted in Tutorial | Tagged , | 4 Comments

Tutorial: Building a Web Browser with UIWebView (Part 1)

Screenshot of the Complete WebBrowser Tutorial

Figure 1: Screenshot of the Complete WebBrowser Tutorial

A new version of this tutorial, updated for Xcode 5 and iOS 7, is available here: Building a Web Browser with UIWebView Revisited (Part 1).

In the next few tutorials you will learn how to use UIWebView to create a simple web browser. The finished browser is shown in Figure 1. You’ll also learn how to create and layout user interface (UI) element programmatically when you cannot get the results you want in the visual editor.

In this tutorial, I will assume you know how to create a project in XCode and you understand the basics of using the visual UI editor (formerly known as Interface Builder). If this is not the case, you might consider doing the UITableView tutorials first. They describe in detail creating a project and the basic use of the visual editor.

To get started create a view based project in XCode and save it as WebBrowser.

Creating the IBOutlets.

Before laying out the UI, you’ll first add the IBOutlets that this first stage of the project will use. Open WebBrowserViewController.h and make the following modifications.

@interface WebBrowserViewController : UIViewController<UIWebViewDelegate> {
    UIWebView* mWebView;
    UIToolbar* mToolbar;
    UIBarButtonItem* mBack;
    UIBarButtonItem* mForward;
    UIBarButtonItem* mRefresh;
    UIBarButtonItem* mStop;
}

@property (nonatomic, retain) IBOutlet UIWebView* webView;
@property (nonatomic, retain) IBOutlet UIToolbar* toolbar;
@property (nonatomic, retain) IBOutlet UIBarButtonItem* back;
@property (nonatomic, retain) IBOutlet UIBarButtonItem* forward;
@property (nonatomic, retain) IBOutlet UIBarButtonItem* refresh;
@property (nonatomic, retain) IBOutlet UIBarButtonItem* stop;

- (void)updateButtons;

@end

Open WebBrowserViewController.m and add the synthesis code at the top of the @implementation section.


//...

@implementation WebBrowserViewController
@synthesize webView = mWebView;
@synthesize toolbar = mToolbar;
@synthesize back = mBack;
@synthesize forward = mForward;
@synthesize refresh = mRefresh;
@synthesize stop = mStop;

//...

You might as well get dealloc and viewDidLoad out of the way too.

- (void)dealloc
{
    [mWebView release];
    [mToolbar release];
    [mBack release];
    [mForward release];
    [mRefresh release];
    [mStop release];
    [super dealloc];
}
- (void)viewDidUnload
{
    self.webView = nil;
    self.toolbar = nil;
    self.back = nil;
    self.forward = nil;
    self.refresh = nil;
    self.stop = nil;
    [super viewDidUnload];
}

Laying Out the User Interface

Simulated Metrics Settings

Figure 2: Simulated Metrics Settings

Click on the WebBrowserViewController.xib file. Click on the view and in the attributes inspector, under simulated metrics, choose navigation bar for the top bar. You can refer to Figure 2 to see what this looks like.

This does not actually add a navigation bar to the user interface, it just simulates one as an aid to layout. Later you will add a custom navigation bar programmatically.


The UI Layout.

Figure 3: The UI Layout.

Drag a UIToolbar from the objects library onto the view and drag it to the bottom. Drag a UIWebView into the central area of the view. In addition to the default bar button on the toolbar, drag three more onto the toolbar. Adjust the identifiers for the bar button items to be Rewind, Stop, Refresh and Fast Forward. Finally insert three flexible space bar button items one between each pair of bar button items to space them out.

Connecting the UI and Code

File's Owner IBOutlet Connections

Figure 4: File's Owner IBOutlet Connections

To connect up the IBOutlets you added in WebBrowserViewController.h at the start of the tutorial, click on the file’s owner icon and switch to the connections inspector. Drag from each of the outlets onto the corresponding UI element. When you are finished the connections inspector should look like Figure 4.


UIWebView IBActions

Figure 5: UIWebView IBActions

In addition to IBOutlets, the visual editor can connect actions performed on UI elements to methods in the code known as IBActions. With the connections inspector still selected, click on the web view. You’ll notice a list of received actions: goBack, goForward, reload and stopLoading. Connect each of these actions to their respective bar buttons by dragging from the empty circle to the bar button item. When you are finished your connections inspector should look like Figure 5.

The UIWebViewDelegate

You next task is to add the methods from the UIWebViewDelegate protocol to WebBrowserViewController.m.

// MARK: -
// MARK: UIWebViewDelegate protocol
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    return YES;
}

- (void)webViewDidStartLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    [self updateButtons];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    [self updateButtons];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    [self updateButtons];
}

The method shouldStartLoadWithRequest is called before the browser starts loading content and could, for example, be used to implement an ad blocker. In your case just return YES for the moment. The other methods should be relatively self explanatory and the code you added to them is to show and hide the network activity indicator in the status bar and to update the states of the toolbar buttons. You’ll also need to add the code to update the buttons:

- (void)updateButtons
{
    self.forward.enabled = self.webView.canGoForward;
    self.back.enabled = self.webView.canGoBack;
    self.stop.enabled = self.webView.loading;
}

This code uses properties of UIWebView to determine which buttons should be enabled.

Post-load Configuation: viewDidLoad

You’re almost ready for your first test run, just a few more lines of code.

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSAssert(self.back, @"Unconnected IBOutlet 'back'");
    NSAssert(self.forward, @"Unconnected IBOutlet 'forward'");
    NSAssert(self.refresh, @"Unconnected IBOutlet 'refresh'");
    NSAssert(self.stop, @"Unconnected IBOutlet 'stop'");
    NSAssert(self.webView, @"Unconnected IBOutlet 'webView'");

    self.webView.delegate = self;
    self.webView.scalesPageToFit = YES;
    NSURL* url = [NSURL URLWithString:@"https://iosdeveloperzone.com"];
    NSURLRequest* request = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:request];
    [self updateButtons];
}

Lines 4-7 are there to detect missing IBOutlet connections.
Line 5 sets you view controller as the delegate for the web view. If you don’t do this, none of the methods you added in the last section would be called. Lines 11-13 programmatically load a default URL. Since you haven’t added the address bar yet there is no way for the user to enter a URL. Later this code will load the user’s home page.

At this points you should be able to build your project and a web page loaded. Tomorrow you’ll add the address bar.

If your code does not work you may want to compare it to the completed code for this part of the tutorial: WebBrowser1.zip

This site is ad supported. Please consider visiting our sponsors while downloading.


Posted in Tutorial | Tagged , | 7 Comments

Coming this weekend: UIWebView

There’s still more to learn about UITableView but just to vary things a little I am going to post some code showing how to build a simple web browser using UIWebVIew. Check back over the weekend!

Posted in News | Tagged , | Leave a comment