Channels ▼
RSS

Web Development

UIWEBVIEW: The iPhone's Built-in Browser, Part 1


Time to Code

Open MainNavigationView.xib.cs in MonoDevelop. We're going to make a few small changes, first of all, take a look at the third constructor:


public MainNavigationView () : base("MainNavigationView", null)
{
	Initialize ();
}

Let's change that to this:


public MainNavigationView ()
{
	NSBundle.MainBundle.LoadNib ("MainNavigationView", this, null);
	Initialize ();
}

These two calls look very similar, because they are. The base class constructor loads the xib file, but does it asynchronously, which would cause our application to error (we're going to access an item in the xib file right after our constructor, in just a bit). The second implementation forces the xib file to load synchronously, so we can access its members right away.

The next thing we want to do is add the following property:


public UINavigationController NavController
{
	get { return this.navMain; }
}

We're simply exposing the view's Navigation Controller. We'll see why we do this in just a bit, but basically, we'll need to get access to it so we can add it to our main window.

Next, in the Initialize method, let's add the following code:


this.btnWebBrowser.TouchDown += delegate {
	this.navMain.PushViewController (new WebBrowserView (), true);
};

This wires up the TouchDown (think "Click" in the Microsoft Windows world) to load a new view controller onto the window. In this case, when someone clicks on our Web Browser button, we're going to instantiate a new WebBrowserView object and push it onto our Navigation Controller. Your MainNavigationView.xib.cs class should now look like this:


using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;

namespace Example_UIWebView
{
	public partial class MainNavigationView : UIViewController
	{
		#region Constructors

		// The IntPtr and initWithCoder constructors are required for controllers that need 
		// to be able to be created from a xib rather than from managed code

		public MainNavigationView (IntPtr handle) : base(handle)
		{
			Initialize ();
		}

		[Export("initWithCoder:")]
		public MainNavigationView (NSCoder coder) : base(coder)
		{
			Initialize ();
		}

		public MainNavigationView ()
		{
			NSBundle.MainBundle.LoadNib ("MainNavigationView", this, null);
			Initialize ();
		}

		void Initialize ()
		{
			this.btnWebBrowser.TouchDown += delegate {
				this.navMain.PushViewController (new WebBrowserView (), true);
			};
		}
		
		#endregion
		
		public UINavigationController NavController
		{
			get { return this.navMain; }
		}
		
	}
}

Now that we've made those changes made to our navigation controller screen, open up the Main.cs file. Add the following declaration to your AppDelegate class:


MainNavigationView _mainNavView;

Then, as the first calls in the FinishedLaunching method, add the following code:


this._mainNavView = new MainNavigationView ();
window.AddSubview (this._mainNavView.NavController.View);

This code instantiates a new MainNavigationView screen, and then adds the view that the Navigation Controller has to the window. The main.cs file should now look like this:


using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;

namespace Example_UIWebView
{
	public class Application
	{
		static void Main (string[] args)
		{
			UIApplication.Main (args);
		}
	}

	// The name AppDelegate is referenced in the MainWindow.xib file.
	public partial class AppDelegate : UIApplicationDelegate
	{
		MainNavigationView _mainNavView;
		
		// This method is invoked when the application has loaded its UI and its ready to run
		public override bool FinishedLaunching (UIApplication app, NSDictionary options)
		{
			// If you have defined a view, add it here:
			// window.AddSubview (navigationController.View);
			
			this._mainNavView = new MainNavigationView ();
			window.AddSubview (this._mainNavView.NavController.View);
			
			window.MakeKeyAndVisible ();
			
			return true;
		}

		// This method is required in iPhoneOS 3.0
		public override void OnActivated (UIApplication application)
		{
		}
	}
}

Let's run the application and make sure it works. From the menu in MonoDevelop, choose Run : Debug, or click the Apple Key and "Enter." You should see your home screen:

[Click image to view at full size]

And if you click on the Web Browser button, the web browser screen should pop on:

[Click image to view at full size]

Of course, it doesn't do anything yet, so let's code up our browser. Close the app in your simulator and open up WebBrowserView.xib.cs in MonoDevelop. Add the following method to your WebBrowserView class:


public override void ViewDidLoad ()
{
	base.ViewDidLoad ();
	
	//---- set the title
	this.Title = "Browser";
	
	//---- wire up event handlers
	this.btnBack.TouchDown += HandleBtnBackhandleTouchDown;
	this.btnForward.TouchDown += HandleBtnForwardhandleTouchDown;
	this.btnStop.TouchDown += HandleBtnStophandleTouchDown;
	this.btnGo.TouchDown += HandleBtnGohandleTouchDown;
	this.txtAddress.ShouldReturn += HandleEditingDone;
	this.webMain.LoadStarted += LoadStarted;
	this.webMain.LoadFinished += LoadingFinished;
	this.webMain.LoadError += LoadError;
	
	//---- disable our buttons to start
	this.btnBack.Enabled = false;
	this.btnForward.Enabled = false;
	this.btnStop.Enabled = false;
	
	//---- navigate to google
	this.txtAddress.Text = "google.com";
	this.NavigateToUrl ();
}

The ViewDidLoad method runs after the view loads. The first thing we're doing here is setting the title of our page, which shows up in the Navigation bar at the top. Next, we wire up our event handlers to handle various events on the page and our web view. Next, we disable our buttons, since there is no browsing history yet (and we'll enable our stop button as soon as we start loading something). Finally, we set our start address to be google.com, and then we call our NavigateToUrl method, which we'll add next:


protected void NavigateToUrl ()
{
	string url = this.txtAddress.Text;
	
	//---- make sure it's prefixed with either https:// or http://
	if (!(url.StartsWith ("http://") || url.StartsWith ("https://")))
	{
		url = "http://" + url;
	}
	
	this.webMain.LoadRequest (new NSUrlRequest (new NSUrl (url)));
}

This is a pretty simple method, we simply check to make sure the address starts with a valid http string, and then we tell our UIWebView control to load our URL.

Let's add one more method, then add all our event handlers:


protected void SetBackAndForwardEnable ()
{
	this.btnBack.Enabled = this.webMain.CanGoBack;
	this.btnForward.Enabled = this.webMain.CanGoForward;
}

This method is also pretty self-explanatory. We're simply enabling our back and forward buttons based on whether the Web View control has a forward or backwards history. It keeps track of that for us, so we don't have to. We'll call this method in our handlers.

Let's add our handlers. The first one is probably the trickiest and has nothing to do with the Web View:


protected bool HandleEditingDone (UITextField textBox)
{
	textBox.ResignFirstResponder ();
	NavigateToUrl ();
	return true;
}

This is wired up to our txtAddress control, and is called when the user hits the "enter" button (which in our case is "Go," since we set it to that in Interface Builder). The ResignFirstResponder method hides the keyboard. We then call our NavigateToUrl method to load the URL the user just entered.

Let's add some more handlers:


protected void HandleBtnGohandleTouchDown (object sender, EventArgs e)
{
	NavigateToUrl ();
}

protected void HandleBtnStophandleTouchDown (object sender, EventArgs e)
{
	this.webMain.StopLoading ();
}

protected void HandleBtnForwardhandleTouchDown (object sender, EventArgs e)
{
	if (this.webMain.CanGoForward)
	{
		this.webMain.GoForward ();
	}
}

protected void HandleBtnBackhandleTouchDown (object sender, EventArgs e)
{
	if (this.webMain.CanGoBack)
	{
		this.webMain.GoBack ();
	}
}

The first handler, HandleBtnGohandleTouchDown simply loads the URL that is in the text box. The next handler tells the UIWebView to stop loading the request, when the user clicks the stop button. The last two handlers navigate forward or backward in the browser history, if they UIWebView allows it.

Let's add a couple more handlers:


public void LoadStarted (object source, EventArgs e)
{
	this.btnStop.Enabled = true;
	this.SetBackAndForwardEnable ();
	this.imgBusy.StartAnimating ();
}

public void LoadingFinished (object source, EventArgs e)
{
	this.SetBackAndForwardEnable ();
	this.btnStop.Enabled = false;
	this.imgBusy.StopAnimating ();
}

These handlers run when the browser starts and finishes a request. We enable/disable our buttons, and animate our Busy Indicator as appropriate.

As you can see, the UIWebView control is super easy to work with. We've got one more handler to add, which is a really important one:


public void LoadError (object sender, UIWebErrorArgs e)
{
	this.imgBusy.StopAnimating ();
	this.btnStop.Enabled = false;
	this.SetBackAndForwardEnable ();
	//---- show the error
	UIAlertView alert = new UIAlertView ("Browse Error"
		, "Web page failed to load: " + e.Error.ToString ()
		, null, "OK", null);
	alert.Show ();
}

If you're publishing your application to the App Store, Apple will test your application both when the Internet is available, and when it isn't. If you don't handle not having Internet access gracefully by informing your user, your app is almost certain to get rejected.

In our case, we show a UIAlertView with the error information.

That's it! Your WebBrowserView.xib.cs file should now look like this:


using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;

namespace Example_UIWebView
{
	public partial class WebBrowserView : UIViewController
	{
		#region Constructors

		// The IntPtr and initWithCoder constructors are required for controllers that need 
		// to be able to be created from a xib rather than from managed code

		public WebBrowserView (IntPtr handle) : base(handle)
		{
			Initialize ();
		}

		[Export("initWithCoder:")]
		public WebBrowserView (NSCoder coder) : base(coder)
		{
			Initialize ();
		}

		public WebBrowserView () : base("WebBrowserView", null)
		{
			Initialize ();
		}

		void Initialize ()
		{

		}
		
		#endregion
		
		public override void ViewDidLoad ()
		{
			base.ViewDidLoad ();
			
			//---- set the title
			this.Title = "Browser";
			
			//---- wire up event handlers
			this.btnBack.TouchDown += HandleBtnBackhandleTouchDown;
			this.btnForward.TouchDown += HandleBtnForwardhandleTouchDown;
			this.btnStop.TouchDown += HandleBtnStophandleTouchDown;
			this.btnGo.TouchDown += HandleBtnGohandleTouchDown;
			this.txtAddress.ShouldReturn += HandleEditingDone;
			this.webMain.LoadStarted += LoadStarted;
			this.webMain.LoadFinished += LoadingFinished;
			this.webMain.LoadError += LoadError;
			
			//---- disable our buttons to start
			this.btnBack.Enabled = false;
			this.btnForward.Enabled = false;
			this.btnStop.Enabled = false;
			
			//---- navigate to google
			this.txtAddress.Text = "google.com";
			this.NavigateToUrl ();

		}

		protected void NavigateToUrl ()
		{
			string url = this.txtAddress.Text;
			
			//---- make sure it's prefixed with either https:// or http://
			if (!(url.StartsWith ("http://") || url.StartsWith ("https://")))
			{
				url = "http://" + url;
			}
			
			this.webMain.LoadRequest (new NSUrlRequest (new NSUrl (url)));
		}

		protected void SetBackAndForwardEnable ()
		{
			this.btnBack.Enabled = this.webMain.CanGoBack;
			this.btnForward.Enabled = this.webMain.CanGoForward;
		}

		#region event handlers

		protected bool HandleEditingDone (UITextField textBox)
		{
			textBox.ResignFirstResponder ();
			NavigateToUrl ();
			return true;
		}

		protected void HandleBtnGohandleTouchDown (object sender, EventArgs e)
		{
			NavigateToUrl ();
		}
		
		protected void HandleBtnStophandleTouchDown (object sender, EventArgs e)
		{
			this.webMain.StopLoading ();
		}
		
		protected void HandleBtnForwardhandleTouchDown (object sender, EventArgs e)
		{
			if (this.webMain.CanGoForward)
			{
				this.webMain.GoForward ();
			}
		}
		
		protected void HandleBtnBackhandleTouchDown (object sender, EventArgs e)
		{
			if (this.webMain.CanGoBack)
			{
				this.webMain.GoBack ();
			}
			
		}
		
		public void LoadStarted (object source, EventArgs e)
		{
			this.btnStop.Enabled = true;
			this.SetBackAndForwardEnable ();
			this.imgBusy.StartAnimating ();
		}
		
		public void LoadingFinished (object source, EventArgs e)
		{
			this.SetBackAndForwardEnable ();
			this.btnStop.Enabled = false;
			this.imgBusy.StopAnimating ();
		}

		public void LoadError (object sender, UIWebErrorArgs e)
		{
			this.imgBusy.StopAnimating ();
			this.btnStop.Enabled = false;
			this.SetBackAndForwardEnable ();
			//---- show the error
			UIAlertView alert = new UIAlertView ("Browse Error"
				, "Web page failed to load: " + e.Error.ToString ()
				, null, "OK", null);
			alert.Show ();
		}

		#endregion
	}
}

Let's run it! Your web browser should now load up Google.com:

[Click image to view at full size]

Or, if you don't have Internet access, you'll get the alert with the error:

[Click image to view at full size]

As you can see, by utilizing the UIWebView control, it's incredibly easy to add a full-featured browser to your application. In the Part 2 of this article, I'll extend this application to allow it to utilize the UIWebView as a document viewer to browse local content.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 
Dr. Dobb's TV