Pimping the content of a (Monotouch) iPhone app with a webview | Peter van Ooijen

:

At first sight iOS offers a rich set of UI elements containing things like navigation- tool- and tab-bars, buttons,  spinners and scrolling views. When it comes to presenting text content there is the textview. Which does offer scrolling and automatic links for Utl’s and phone numbers  but dos not have a lot of possibilities to make the content look really good. The main reason is that all text in the view is displayed in one and the same font. No way to even just emphasize a single word in bold.

Back to HTML

For the remainder of this story I assume you are somewhat familiar with basic HTML and Monotouch  or other iOS tools and will do a fast drive through.

To make your content look better you need a WebView. At first sight this component seems intended to display online web-based content. Feeding it with local offline content, straight from code is just as easy. You can drop and size an UIWebView anywhere on a View (or instantiate one from code)  and make it accessible to the code via an outlet. The webview has a method LoadHtmlString to feed it with content.

myWebView.LoadHtmlString(MyMarkup, null);

MyMarkup is the string containing Html content for the view. I will describe the basic possibilities for this content in a HTMLbuilder demo class which uses a stringbuilder to assemble the markup.

Initialization in the constructor:

  • Initializes the stringbuilder
  • Create the <head> of the page
  • Create a  css-style sheet for the basic look of the “page”
  • Write the opening <body> tag

After that the various methods build the content. These here are just some examples, just to get the idea. The assumptions made here

  • All pages have the same fixed font set in the stylesheet.
  • All images are in the Images folder
  • All inline images have the same size

I leave it up to your own imagination where, what and how to fix.

The Markup property (when necessary) writes the page closing tags and returns the html for the “page”. This result is fed straightly to the webview’s LoadHtmlString method.

public class HTMLbuilder

{

    private StringBuilder html;

    private bool closed = false;

    public HTMLbuilder ()

    {

        html = new StringBuilder ();

        html.Append (“<html>”);

        html.Append (“<head>”);

        html.Append (“<style type = \”text/css\”>”);

        html.Append (“body {font-family:helvetica;font-size:15;}”);

        html.Append (“</style>”);

        html.Append (“</head>”);

        html.Append (“<body>”);

    }

 

    public string Markup

    {

        get

        {

            if (!closed)

            {

                closed = true;

                html.Append(“</body”);

                html.Append(“</html>”);

            }

            return html.ToString();

        }

    }

 

    public void Append(string content)

    {

        html.Append(content);

    }

 

    public void AppendBold(string content)

    {

        html.Append(string.Format(“<b>{0}</b>”, content));

    }

 

    public void AddParagraph(string tekst)

    {

        html.Append (string.Format (“<p>{0}</p>”, tekst));

    }

 

    private string ImagePath (string image)

    {

        return NSBundle.MainBundle.PathForResource (string.Format (“Images/{0}”, image), “png”);

    }

 

    public string InlineImage(string image)

    {

        return string.Format(“<img src=\”file:{0}\” width=\”22\” height=\”22\” valign=\”bottom\”>”, ImagePath(image));

    }

 

    private void AddButton(string image, string urlpart)

    {

        var url = string.Format (“<a href=\”{1}\”><img src=\”file:{0}\”></a>”, ImagePath(image), urlpart);

        AddParagraph (url);

    }

 

}

 

The images and buttons require some explanation. A webview expects its resources, like an image, somewhere relative to it’s URL. The Url is passed in the second parameter of the LoadHtmlString method. Here we are working local, inside the iPhone, there is no URL for that, as a second parameter I passed null. The ImagePath method translates the path of an image, which is a  folder in the application, to a path reachable by the webview. The resulting url is fed to an <img> in the InLineImage or AddButton method.

Start surfing

The webview is also a great place for the user to start surfing the web. In the HTMLBuilder the AddButton method creates a clickable image which will take the user to an url outside the app. Like the company website. Clicking this button will fire up Mobile Safari which takes the user out of the app and into the site. The image turns into a linkbutton by embedding it in plain <a> tags.

To steer this behavior the webview uses a WebViewDelegate

public class WebViewDelegate : UIWebViewDelegate

{

    public WebViewDelegate ()

    {

    }

 

    public override bool ShouldStartLoad (UIWebView webView, MonoTouch.Foundation.NSUrlRequest request, UIWebViewNavigationType navigationType)

    {

 

        if (request.Url.Scheme.StartsWith (“http”)) {

            UIApplication.SharedApplication.OpenUrl (request.Url);

            return false;

        }

        return true;

    }

}

 

The delegate checks the Url. When it is a http request it fires up the browser using SharedApplication.OpenUrl. Else the WebView is free to proceed.

The delegate is set in the ViewDidLoad

public override void ViewDidLoad ()

{

    WebViewDelegate = new WebViewDelegate ();

    myWebView.WeakDelegate = WebViewDelegate;

 

It is advised to remove the delegate when unloading

public override void ViewDidUnload ()

{

    myWebView.WeakDelegate = null;

    base.ViewDidUnload ();

}

 

More than HTML

So far we have seen the basics of pimping the content using HTML. The possibilities here are almost endless and Mobile Safari does work with HTML5.

What HTML does not give you are specific iPhone features like gestures and the possibility to hook your own app code (behindSmile) in the page. The good thing is that the webview is a view like all other views, accessible to all  your coding skills. As a demo I will add swiping to a webview. When the users swipes left or right the content of the webview is updated. Not by the browser, but my own code.

The hard thing is that gesture-recognizers are not under quite the same control as the view, they have a different life cycle. Setting them up requires some fiddling. Doing that wrong will lead to a complete crash of the app.

The methods handling the swipes are marked with an Export attribute.

[Export(“SwipeLeft”)]

public void SwipeLeftAction (UISwipeGestureRecognizer recognizer)

{

    myWebview.LoadHtmlString(MyMarkup.prevPage(), null);

}

 

[Export(“SwipeRight”)]

public void SwipeRightAction (UISwipeGestureRecognizer recognizer)

{

    myWebView.LoadHtmlString(MyMarkup.NextPage(), null);

}

 

After a swipe the content is updated to my taste.

Both the handlers should also be added as static properties of the view

public static Selector SwipeLeft {

    get { return new Selector (“SwipeLeft”); }

}

 

public static Selector SwipeRight {

    get { return new Selector (“SwipeRight”); }

 

Wiring up is done in the ViewDidLoad. Here it is absolutely essential to check whether the view already responds to the recognizer

if (!View.RespondsToSelector (SwipeRight)) {

    var swipeRight = new UISwipeGestureRecognizer ();

    swipeRight.AddTarget (this, SwipeRight);

    swipeRight.Direction = UISwipeGestureRecognizerDirection.Right;

    swipeRight.Delegate = new UIGestureRecognizerDelegate ();

    View.AddGestureRecognizer (swipeRight);

}

 

What the gesture recognizer will respond to should be self explanatory. The complexity of the wiring does show iOS is behind the scenes somewhat more complicated that the Monotouch framework can hide.

Summing up

All of this leads to this result

public partial class MyPageWithWebView : UIViewController

{

    #region Constructors

 

    #endregion

 

    private WebViewDelegate WebViewDelegate;

 

    public override void ViewDidLoad ()

    {

        WebViewDelegate = new WebViewDelegate ();

        myWebView.WeakDelegate = WebViewDelegate;

 

        if (!View.RespondsToSelector (SwipeRight)) {

            var swipeRight = new UISwipeGestureRecognizer ();

            swipeRight.AddTarget (this, SwipeRight);

            swipeRight.Direction = UISwipeGestureRecognizerDirection.Right;

            swipeRight.Delegate = new UIGestureRecognizerDelegate ();

            View.AddGestureRecognizer (swipeRight);

        }

 

        if (!View.RespondsToSelector (SwipeLeft)) {

            var swipeLeft = new UISwipeGestureRecognizer ();

            swipeLeft.AddTarget (this, SwipeLeft);

            swipeLeft.Direction = UISwipeGestureRecognizerDirection.Left;

            swipeLeft.Delegate = new UIGestureRecognizerDelegate ();

            View.AddGestureRecognizer (swipeLeft);

        }

 

        base.ViewDidLoad ();

    }

 

    public override void ViewDidUnload ()

    {

        myWebView.WeakDelegate = null;

        base.ViewDidUnload ();

    }

 

    [Export(“SwipeLeft”)]

    public void SwipeLeftAction (UISwipeGestureRecognizer recognizer)

    {

        myWebView.LoadHtmlString(MyMarkup.prevPage(), null);

    }

 

    [Export(“SwipeRight”)]

    public void SwipeRightAction (UISwipeGestureRecognizer recognizer)

    {

        myWebView.LoadHtmlString(MyMarkup.NextPage(), null);

    }

 

    public static Selector SwipeLeft {

        get { return new Selector (“SwipeLeft”); }

    }

 

    public static Selector SwipeRight {

        get { return new Selector (“SwipeRight”); }

 

}

 

Now I have the possibilities of pimping my content using HTML without giving up the power of C# code.