Custom UIWebView Navigation Controller

Download Drill Down Example
+ Controller Source
(1.6MB .zip)

The next version of NetSketch will include a community browser, allowing you to view uploaded drawings, watch replays, and leave comments without leaving the app. When I started working on the community interface, I looked to other apps for inspiration. Almost every app I’ve used on the iPhone use a sliding navigation scheme, giving you the feeling that you’re drilling down into content as you use the application. This interface is intuitive in a lot of contexts, and dates back to the original iPod. The Facebook app allows you to browse other people’s Facebook pages and uses a drill down navigation bar. This works well for the social-network space because you can drill down to look at information and then return to the first page quickly.

I decided to use a UINavigationBar and implement a similar drill-down interface for the community part of NetSketch. However, I didn’t want to create custom controllers for each page in the community. I wanted to be able to improve the community without updating the app, and I didn’t want to write a communication layer to download and parse images and custom XML from the server.

Using a UIWebView seemed like the obvious choice. It could make retrieving content more efficient, and pages could be changed on the fly. With WebKit’s support for custom CSS, I could make the interface look realistic and comprable to a pile of custom-written views.

I quickly realized that it wasn’t all that easy to implement “drill down” behavior with a UIWebView. Early on, I ruled out the possibility of creating a mock navigation bar in HTML. Since Safari on the iPhone doesn’t support scrolling iframes or “position:static”, “position:fixed” CSS tags, there was no good way to make the bar sit at the top of the screen while allowing the user to scroll. I decided that a native UINavigationBar would be more practical and provide a better user experience. However, the default UINavigationController class was built to use separate controllers for each layer, and doesn’t worry about freeing up memory as the hierarchy grows. Since web pages can be very large, I decided that I should follow suit with Mobile Safari and keep less than eight in memory at once.

I tried several solutions, and finally created a custom DrillDownWebController class with a manually managed UINavigationBar to handle the interface. Here’s a video of it in action:

Video thumbnail. Click to play
Click To Play

The DrillDownWebController maintains a “stack” of DrillDownPages, with each page representing a single layer in the drill-down hierarchy. A DrillDownWebController can be a root level controller, or it can be loaded into an existing UINavigationController. When it appears, it silently swaps its parent’s navigation bar with it’s own and loads the first page.

The DrillDownPage is a wrapper for a UIWebView that acts as its delegate and provides higher-level access to important properties of the page, such as it’s title. I decided to pull this functionality out of the controller because it reduced confusion when multiple UIWebViews were being loaded. When the user clicks a link in the top level web view, a new DrillDownPage object is created and it begins loading the requested page in an invisible UIWebView. The controller locks the current page and displays an activity indicator in the top right corner of the navigation bar. When loading is complete, it animates a slide to the new page and updates the navigation bar. All the other pages in the page “stack” are notified that their position in the drill-down hierarchy has changed.

The notification step is important, because Page objects need to perform cleanup operations if they are far up the hierarchy. To save memory, they take an image of their UIWebView and release it, displaying the image as a placeholder. If the user backs up the hierarchy and the page becomes visible, a progress indicator is displayed and the actual page is reloaded.The DrillDownWebController provides several features in addition to the basic drill-down behavior. Meta tags on the HTML pages can be used to add a button to the top right of the UINavigationBar and program it to load a new URL. Custom url handlers can be used to perform custom code in the controller when the user clicks links in the page. This functionality was used in NetSketch to make netsketch:// links execute custom code and play drawing replays.

Also, standard <a> links in a page cause a drill-down animation when the next page is loaded. Form submissions do not cause a drill down. Tiny forms can be used to make some links drill down and others reload the existing page in the hierarchy.I’ve posted source code for the DrillDownWebController as well as an example. I think it will be useful to anyone who wants to implement a drill down interface without writing a whole lot of code.

Enjoy!

Drill Down Example + Controller Source (1.6MB .zip)

  1. hi
    qha36k8gud8gp1md
    good luck

  2. Jean

    Nice work.
    Thanks Ben.

  3. Really nice, i´ve been looking for something like this along time, but I need some help to implement some webview in a tabbar application with the same navigation. The problem is that I make a tabbarcontroller and I don´t know how to show the webview with the navigationbar. I´ve tried several methods but no solution. Could anybody help me whith this problem??
    Thanks

  4. Hello, thanks a lot for posting the example I found it very helpful and went and bough the NetSketch app. I’m newer than new at this… I’m trying to make a little app for my band and I was wondering how I can make it start from the webview, in other words instead of having a tableview with a list of urls that then shows a webpage, I’d like to start with a webpage ( maybe with the UIWebview) and retain the same functionality as in your example from that point on. same as the example minus the tableview at the start. Thanks a lot for your help!

  5. Mike

    This is great but I have one question - How could I load a local html file contained within the app?

  6. Ben Gotow

    Hey Mike,

    I think you can get the absolute path to a file and provide it as the base url in the DrillDownWebController’s init function. For example, let’s say you had a file that was stored at ~/Documents/index.html relative your app. You should be able to say:

    NSString * path = @”~/Documents/index.html”;
    NSString * absolutePath = [NSString stringByExpandingTildeInPath: path];

    and then pass absolutePath to the controller. If that doesn’t work, you could try appending the “file://” url handler to the front of the string. I don’t think that should be necessary, though. Also, you can use the NSBundle to get the relative path to files in your app if you don’t know them offhand (for instance if you drag the html file into XCode and build, it’s put inside your bundle).

    Hope that helps!

    - Ben

  7. Mike

    Thanks for the hints ben,

    I tried your suggestions with the paths & in RootViewController.m I changed as follows:

    - (void)viewDidLoad
    {
    pageURLs = [[NSArray alloc] initWithObjects:
    @”http://www.gotow.net/creative/content_blog/drilldowncontroller/example/”,
    @”index.html”, // My local File
    nil];
    pageTitles = [[NSArray alloc] initWithObjects:
    @”Basic Usage”,
    @”Local File”,
    nil];

    }

    However no luck loading the local html.

  8. Lee Smith

    I like it! How would you suggest making a Navigation Bar direct to another Navigation Bar when clicked (sometimes, but othertimes open a web page)?

  9. John

    I have been trying to get this to work with local html files also.

    I was thinking that the following line in DrillDownWebController.m would need to be changed:
    [p setRequest: [NSURLRequest requestWithURL: [NSURL URLWithString: [p actionURL]]]];

    To something like :
    [p setRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] ]]]];

    and then change the RootViewController.m to:
    pageURLs = [[NSArray alloc] initWithObjects:@”your folder/your page”,@”your folder/your page”,nil];

    still cant get this to show local pages. Any help would be greatly appreciated.

  10. tkirby

    I got it to show local pages after changing URLWithString to fileURLWithPath in DrillDownWebController.m and commenting out…

    /*if (![[[[r URL] absoluteString] substringToIndex:4] isEqualToString:@”http”]){
    [container handleNonHTTPRequest: r];
    return NO;
    }*/

    …in the shouldStartLoadWithRequest method of DrillDownPage.m

Reply to 'Custom UIWebView Navigation Controller'