WebKit on the iPhone (Part 1)

If you develop an application which should display a web page or HTML file, you can use the WebKit framework, which is part of the MacOS and also of the iPhone OS.

But while on the Mac, the WebKit framework provides almost 160 public header files which define even more public classes and tons of methods you can call to get control of all aspects of loading, rendering, displaying and modifying a web page, on the iPhone there’s only one single class (UIWebView) with has just about a dozen methods you can use. Though internally the UIWebView class uses the same WebKit framework that is available on the Mac as public API, this API is private on the iPhone and therefore can’t be used. The small number of methods of the UIWebView class are sufficient to display nicely formatted text in help screens for example, but for a web browser (like iCab Mobile) or other web-based apps this isn’t enough, many essential features are missing.

Some examples:

  • UIWebView doesn’t provide a method to get the title of the currently displayed web page,
  • it just ignores all attempts to open links which are meant to open in new windows or tabs
  • it doesn’t allow accessing the HTML tree

WebKit itself provides many classes for all these tasks, but all of them are private and not available on the iPhone.

Some of the alternative browsers which are available in the AppStore just declare these limitations as feature (for example they advertise the inability to open new windows or Tabs as “no anoying popup window”). This sounds great, but of course this doesn’t make such browsers useful in the real world.

So what can we do to overcome these limitations of the UIWebView class? Can we (re)implement all the cool features of the WebKit framework which is available on the Mac on the iPhone as well without violating the  iPhone SDK agreements with Apple? Unfortunately, we can’t. But we can implement many of the missing feature.

If you look at the available methods, there’s only one, which would allow access to the content of the web page, and this is more or less the only way to get back  the missing features. And this method is

- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

This method is used to execute JavaScript code in the context of the current web page and get back a string as result. This means that we have to use JavaScript code to implement the features we need.

Let’s start with something that is very easy. We implement methods to get the title and the URL of the currently displayed web page. We implement this as an Objective C “Category”, so we don’t need to subclass UIWebView:

File: MyWebViewAdditions.h

@interface UIWebView (MyWebViewAdditions)
- (NSString*)title;
- (NSURL*)url;

File: MyWebViewAdditions.m

#import "MyWebViewAdditions.h"

@implementation UIWebView (MyWebViewAdditions)

- (NSString*)title
    return [self stringByEvaluatingJavaScriptFromString:@"document.title"];

- (NSURL*)url
    NSString *urlString = [self stringByEvaluatingJavaScriptFromString:@"location.href"];
    if (urlString) {
        return [NSURL URLWithString:urlString];
    } else {
        return nil;


What are we doing here?
From the JavaScript’s point of view a web page is represented by the “document” object which has several properties. One of the properties is the “title” property which contains the title of the page. So with “document.title” we can access the title of the document within JavaScript. And this is exactly what we need to pass as a parameter to the method “stringByEvaluatingJavaScriptFromString:” to get the document title.

For retreiving the URL we do something similar.

So whenever we need to get the title or URL of the web page that is displayed in a UIWebView object, we only need to call the “title” or “url” method:

NSString *title = [anyUIWebViewObject title];

The next limitation we may want to address is the inability to open links which would open in a new window. The WebKit on the Mac would just call a delegate method of the host application to request that a new WebView object is created for an URL request. The application would then create a new WebView object and load the new page there. But on the iPhone the UIWebView doesn’t support such a delegate method and so all attempts to open such a link are just ignored.

These links do usually look like this:

<a href="destination" target="_blank">Link Text</a>

The “target” attribute defines where the link will open. The value can be a name of a frame (if the web page has frames), the name of a window or some reserved target names like “_blank” (opens a new window), “_self” (the window itself), “_parent” (the parent frame, if there are nested frames) and “_top” (the top-level or root frame, or identical to “_self” if the page doesn’t use frames).

As a first step, we want to tap on a such a link in our iPhone App, and the link should open like any other normal link in the same UIWebView object. What we need to do is simple: we need to find all links with a “target” attribute set to “_blank” and change its value to “_self“. Then the UIWebView object will no longer ignore these links. To be able to modify all of the link targets we have to wait until the page has finished loading and the whole web page content is available. Fortunately UIWebView provides the delegate method

- (void)webViewDidFinishLoad:(UIWebView *)webView;

which will be called when the web page has finished loading. So we have everything we need: We get notified when the page has loaded, and we know a way to access and modify the web page content (using “stringByEvaluatingJavaScriptFromString:“).

First we write our JavaScript code. Because this will be a little bit more code than what was needed to get the document title, it’s a good idea to create an extra file for our JavaScript code and then we add this file to the resources of our project in XCode:

File: ModifyLinkTargets.js:

function MyIPhoneApp_ModifyLinkTargets() {
    var allLinks = document.getElementsByTagName('a');
    if (allLinks) {
        var i;
        for (i=0; i<allLinks.length; i++) {
            var link = allLinks[i];
            var target = link.getAttribute('target');
            if (target && target == '_blank') {

What is this JavaScript function doing, when called?
It gets an array of all links (“a” tags) and then loops through all of these tags, checks if there’s a target attribute with the value “_blank“. If this is the case it changes the value to “_self“.

Note: There are other tags which can have a “target” attribute, like the “form” tag and the “area” tag. So you can use the “getElementsByTagName()” call to get these tags as well and modify their target attributes in the same way as I’ve done this for the “a” tag.

In our iPhone App we need to define a delegate for the UIWebView object and this delegate object will be called whenever the web page has finished loading. This is the method that is called in the delegate by the UIWebView object:

- (void)webViewDidFinishLoad:(UIWebView *)webView
    NSString *path = [[NSBundle mainBundle] pathForResource:@"ModifyLinkTargets" ofType:@"js"];
    NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];

    [webView stringByEvaluatingJavaScriptFromString:jsCode];

    [webView stringByEvaluatingJavaScriptFromString:@"MyIPhoneApp_ModifyLinkTargets()"];

What is this code doing?
At first the access path of the JavaScript file we created before is retreived from the application bundle and we load the content of the file (the JavaScript code) into a string. Then we execute/inject this code into the web page and finally we call out JavaScript function
which is modifying the link targets.

Some notes:

  • Getting the JavaScript file from the application bundle and loading it into a string should be usually done somewhere in the init methods of your UIWebView delegate object. This way the string with our JavaScript code is only loaded once and can be simply reused whenever a new link is clicked and a new page is loaded.
  • Using a long name for our JavaScript function which also includes a prefix like “MyIPhoneApp_” makes it unlikely that the code we inject into a web page will interfere or confict with functions and variables which the web page itself has already defined for its own purposes. This is especially important when we modify web pages we haven’t created ourselves and where we can not predict which function or variable names the JavaScript code of the web page is already using.
  • Using separate calls of “stringByEvaluatingJavaScriptFromString” to first injecting our own JavaScript code and then calling our own JavaScript function to start modifying the link targets seems to be more complicated that necessary. And for this simple example you would be right. But it is likely that you’ll define much more additional JavaScript functions for many different tasks as well. Some of the tasks are started when the page has finished loading (like modifying the link targets), but some tasks will be started later and maybe even multiple times. And so it makes much sense that injecting the code and calling the functions are done in separate calls.
  • The delegate method “webViewDidFinishLoad:(UIWebView *)webView” is called for each frame, not only when the page itself has finished loading. This means that this delegate method can be called multiple times while a single web page is loaded. I think that this can be called a bug in the iPhone OS, but nevertheless it is important to know. When you modify the web page, be aware that this might be done multiple times and so make sure that none of your modifications will have bad side effectes when being modified a second time.

What next?

  • The above example code does not cover web pages where new windows are opened using JavaScript.
  • The links will open in the same window, which is fine because they are no longer ignored. But they still don’t open in a new window or Tab.

More about this topic and the cases which are not yet covered will come in the second part of the “WebKit on the iPhone” article.

Feel free to ask questions and write comments. I’d like to get some feedback.

85 thoughts on “WebKit on the iPhone (Part 1)

  1. Hi,

    I am loading the file in UIWebView and hiding the scroll
    I want to scroll the page programatically by number of lines of text. Any help how can I do that using java script.

  2. I searched I have the same question but there was no answer for this question in blog?

    I am trying to scroll webview to an offset when a button click occurs [Page up and Page down buttons], for that i have to know the following things,

    i) Scrollable content height,
    ii) How to move the webview to an offset of the screen.

    If u have any info , would be appreciated


  3. @akp
    There’s only one way to scroll a web view programmatically, and this is by using the JavaScript function “scrollTo”:

    window.scrollTo(x, y);

    The coordinates are document coordinates, not device coordinates.

  4. Hello Alexander.

    Is there a simple way to make the UIWebView scroll more “smoothly” similar to Safari, where it offloads the offscreen portions with the “gray area”? Could this be done with CATileLayer?

  5. @JC
    There seems to be no public API to get the smooth “checkerboard scrolling” from in Safari.
    In case you find a way to implement this using public APIs only, please let me know.

  6. Pingback: Saving an Image from UIWebView | Steili.com

  7. Thank you for this blog. I’m learning a lot.
    I have a question about using Javascript like you do in some of your other posts. Can you detect when the web page changes due to Javascript? I’m trying to use your method for links with target=”_blank”, but it’s not working on the Google Reader site. Those links are created by javascript after the page has loaded and the user has clicked on an entry. Before that, they don’t have the target attribute. How do you detect links where the target attribute is added by javascript? Thank you.

  8. @Sam
    You won’t get notified when the page changes due to JavaScript. The only chance would be to do some checks yourself. You could do this on touch events or also every x seconds or so.

  9. @ashish
    There’s no simple solution here. You would have to manually measure the line heights of the text to be able to correct the scrolling offset. But please note that it is possible that you can’t find any offset at all which would not cut off a line of text. This can happen when a web page uses multiple columns and the text of these columns is offset by a half line.

    Maybe a better pragmatic solution would be to always scroll less than a full page height. So the last line that is shown before scrolling (fully or only partially) will be shown as the first line after scrolling. This way you don’t need to be really exact.

  10. thanks Alexander, as you are saying that “always scroll less than a full page height”.
    while loading a file to webview, the line space and paragraph spaces we can not determine where they will occur. How would be possible to calculate page height?

  11. also forgot to ask how stanza and other readers are doing it? Are they not using webview?

  12. Hey you said that smooth checkerboarded scrolling is Not possible outside safari but it seems it’s no longer True as many browsers implement this now – could you Please explain me how this works eg why smooth scrolling is Not possible without the side effect of annoying checkerboarding??? Is the whole page converted to bitmap or smth like this so browser is cheating to get smoother scrolling? Checkerboards are highly annoying on IPad 1 (especially the ones that occur during page loading) but jerky scrolling on image intensive sites with browsers that doesnt use this technique is annoying as Well… Is IPad’s Cpu that weak it Cant scroll simple image gallery without choking? And one more thing which I don’t understand – In checkerboarding browsers while page is still loading and you scroll down to checkerboard it won’t dissapear until you stop scrolling and lift your finger – If you don’t checkerboard will remain visible… And it’s Not enough to lift your finger you have stop page from scrolling (“moving”) completly. Why is that? If browser has Scrollbar like icab’s scrollpad and you scroll page using it loading will continue regardless of scroling… 

  13. @Marzzz
    If another browser shows a checkered pattern while scrolling, it uses a private (and therefore forbidden) API. Apple might remove the Apps if they find out.

  14. Hi, This post working best for _blank and _new but for onclick() it doesnt work.
    Any idea how to deal with this ??

  15. @Bhavik
    The “onclick” handler is just an event handler. It is not directly related to links or new windows. So the real question is, what is the “onclick” hander doing exactly?

    BTW: this blog post is in parts outdated. In the latest releases of the iOS, links with a target “_blank” do work now without any additional code (the latest iOS releases will ignore the “_blank” automatically). But of course, if you want to open the links in a new tab/window, you have to use additional code and modify the original links.

  16. Hello,
    I have a HTML content on webview and i wanted to search the string present on the webview.If the string is there i want to set the scroll to that position dynamically.Please give some solution over this problem.


  17. @nikhil
    You need to identify the HTML element which contains the text and then call the Javascript function scrollIntoViewIfNeeded(true) on this element. In one of my other posts in this blog, I’ve shown a way to search within the HTML content. This will automatically create SPAN elements around the found text in order to highlight. And this SPAM element is the one you can use to scroll into the view, wit the functioned mentioned above.

  18. Hi
    I am using the js for searching on UIWebview contents (used from your other post ). searching is fine but the scrolling is not working accordingly, not scrolling view to display highlighted text.
    I called this function to java script span element.

    Any idea?

  19. @Zee

    Calling span.scrollIntoViewIfNeeded(true); should work fine. Please make sure that the “span” variable is really a reference to the correct span element. Also please note that you need wait until the web site has finished loading before you can search and scroll.

  20. Good article
    I have met a trouble. I have a UIWebView, It can load a webpage first time,when i click on it ,i want open a new webView to load, in shouldStartLoadWithRequest i catch UIWebViewNavigationTypeLinkClicked navigationType to open a new webView, but some url open by click,just like ‘window.location.href=’,the navigtaion type is UIWebViewNavigationTypeother, when i do like this
    (navigationType == UIWebViewNavigationTypeLinkClicked || navigationType == UIWebViewNavigationTypeOther){
    It always open in new page besides first time,But it does not what i want.Any suggestion?

  21. @Eric
    “UIWebViewNavigationTypeLinkClicked” is only used when you tap on a normal link. If the web page opens a new page via “location.href” or “window.open()” or when loading a web page via “loadRequest” method of UIWebView, then “UIWebViewNavigationTypeOther” is used. So the “navigationType” is often much too unspecific to be really useful.

    So if you need to open link in a new window only once, you need to remember somewhere that you’ve opened a link already, so the next time you open that link, you
    can prevent that you open it in a new window again.

    The best way to do this depends on what exactly you need to do. You could simply store all the URLs you open in an array together with the information if these should or should not be opened in a new window again. You can also inject some JavaScript into the web page which modifies all links, so they will “change” once you’ve clicked on them the first time (for example by replacing the URL scheme from “http” to a custom one, so you can regingize links you’ve opened in the past by the different custom URL scheme).

  22. Maybe my description is not clear
    My mean that there is a UIWebView inside a UIView(a) and i just want use it one time. When i click the URL on it ,whatever the html open type ,I only want open it in a new view(b) which contained a new UIWebView. The other operation all on the new view(b),and have no relationship with the view(a). Any suggestion?

  23. @Eric
    OK, if you just want to open any link from the first UIWebView in another WebView, then you should simply implement the UIWebView delegates “webViewDidStartLoad:”, “webViewDidFinishLoad:” and “webView:shouldStartLoadWithRequest:navigationType:”.

    When loading the initial page in the first UIWebView, you return YES for all requests in “webView:shouldStartLoadWithRequest:navigationType:”, so the initial page can load. After the page has loaded (which means “webViewDidFinishLoad:” was called as many times as “webViewDidStartLoad:”), you set a flag, and now when “webView:shouldStartLoadWithRequest:navigationType:” is called again, you return NO and instead create the new UIWebView and load the request there.

    There can be still some issues you may need to care about, if the initial web page dynamically loads new stuff automatically (like ads which are exchanged), because these might also end up in “webView:shouldStartLoadWithRequest:navigationType:” even though these are not links. But the iOS does not tell you this. So depending of the web page you need to work with, you may need to do some additional checks to make sure that you only react on the right links.

    One way to do this would be to inject JavaScript code into the initial web page which modifies all the links you want to control, so you can identify those links within the “webView:shouldStartLoadWithRequest:navigationType:” method.

  24. @Alexander
    ok follow your answer ,some urls like “window.location”,how to change or catch?

  25. @Eric
    any assignment to window.location will end up as request in the delegate method “webView:shouldStartLoadWithRequest:navigationType:”, very similar to a clicked link. The navigationType argument is “UIWebViewNavigationTypeOther”. So you catch these in the same way as you do this for links.

  26. @Alexander Thank you for your help
    But when i first load the url ,the navigationType is also UIWebViewNavigationTypeOther, I can’t distinguish the first load or “window.location”, i have set a flag to mark first load.In webView “didstartload” i set it to NO, in webView “finishLoad” to YES.in “webView:shouldStartLoadWithRequest:navigationType ” i judge the flag,but the first time a new page have create.
    I hope you can make a test or demo by my url,This trouble make me unhappy .

  27. Hi, thank you for the amazing post. Can you please also tell about fixing the memory leaks caused by UIWebview s.

  28. @krati
    Unfortunately there’s nothing you can do about the memory leaks of UIWebKit. Only Apple can. And in order to to help Apple in doing so, you (and other developers) should send bug reports to Apple, and also include a sample project which demonstrates the issue. The more bug reports Apple gets, the more likely it is that Apple will investigate the bug. Apple seems to prioritize their bugs by the number of reports they get.

  29. Hello Alexander, Will you going to use WKWebView instead of UIWebView as rending engine in iCab?

    And I found that very very less private API we can use in WKWebView than UIWebView.
    eg, -elementAtPoint: is miss, and We only can customise the contextual menu via injecting javascript, And this will not work in a cross domain iframe.

  30. @Alex Lee
    Currently the WKWebView lacks some essential features (like support for the standard Cocoa Networking components NSURLProtocol, NSURLCache, NSHTTPCookieStorage etc), so in the current state WKWebView is pretty useless. But I still hope that this is all coming. And then I will probably use WKWebView under iOS 8.

    You’re right injecting JavaScript does not work in cross-domain frames, but this is the same for UIWebView and WKWebView. So nothing has changed here if you’re developing for the AppStore (where you can’t use private APIs).

Leave a Reply

Your email address will not be published. Required fields are marked *