Skip to content


WebKit on the iPhone (Part 2)

In the first part of this tutorial you’ve learned that you can use JavaScript code to get information about the web page and to modify links which would be normally ignored by UIWebView (because they are supposed to open a new window) so that they will work just like any other link.

In the second part of the tutorial you’ll learn how to open links (which are supposed to open in a new window) in a new tab. On the iPhone you can’t open multiple windows, so we have to use “tabs” instead of windows. I’ll also show how to deal with opening windows or tabs via JavaScript when no HTML link is involved.

In part one we’ve written the JavaScript function “MyIPhoneApp_ModifyLinkTargets()” which loops through all links and replaces the link target “_blank” with “_self”. This way the links will open in the same tab and are no longer ignored by UIWebView. But if we really want to open these links in new tabs, we have to open a new  tab ourselves and then open the link there. This can’t be directly done within the JavaScript code. So we have to find a way to pass the information about the link URL and the link target to the Objective-C part of our app. The easiest way to do this to replace the original link URL by a new URL which includes all these information. In order to make it easy to recognize our modified URLs, we are creating the new URLs with a new custom URL scheme “newtab”. We just add this to the existing JavaScript function we’ve written in the last part of the tutorial.

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') {
                link.setAttribute('target','_self');
                link.href = 'newtab:'+escape(link.href);
            }
        }
    }
}

The additional line

link.href = 'newtab:'+escape(link.href);

will take the original URL and escapes all the characters with a special meaning (like “:” and “/”) and puts the new custom URL scheme “newtab” in front of it. The link URL “http://www.apple.com/” will be converted to “newtab:http%3A//www.apple.com/” for example.

Within the Objective-C code of our app, we have to implement the UIWebView delegate method

- (BOOL)webView:(UIWebView *)view shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

This delegate method is called whenever a link is tapped by the user. And the app can check the URL and return YES or NO, wether it is OK to open this URL or not. What we need to do is to check if the URL is one of the URLs which were modified by us.  If this is the case we create a new tab with a new UIWebView object and then open the original URL in this new UIWebView object. Finally we return NO as the result of the delegate method to indicate that this modified URL should not be opened. This is important because the page is already loading in the new tab. If we find out that this delegate method is called with an URL that was not modified, we just return YES, so the URL will open as expected. We know that the URL was modified, if the URL uses the URL scheme “newtab”. So the delegate method would look like this:

- (BOOL)webView:(UIWebView *)view shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSURL *url = [request URL];
    if ([[url scheme] isEqualToString:@"newtab"]) {
        NSString *urlString = [[url resourceSpecifier] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        url = [NSURL URLWithString:urlString relativeToURL:[view url]];
        [self openNewTabWithURL:url];
        return NO;
    }
    return YES;
}

The “resourceSpecifier” method of the NSURL object is returning everything but the URL scheme, which is exactly what we need to get the original URL. And because within the JavaScript code we’ve escaped the original URL we need to unescape it. And this can be done with “stringByReplacingPercentEscapesUsingEncoding:“. The method “openNewTabWithURL:” is not shown here, but it is obvious what it is supposed to do: it creates a new tab with a new UIWebView object and opens the URL in the new tab.

In the above code you’ll notice this line:

url = [NSURL URLWithString:urlString relativeToURL:[view url]];

We call “[view url]” here (calling the method we’ve written in the first part of the tutorial) to get the base URL, in case we’ve received only a relative URL from the JavaScript code. For normal links this wouldn’t be necessary because the “href” property of a link element will always return the absolute URL. But when dealing with windows or tabs which are opened using JavaScript code and not from within links, it is possible to receive only relative URLs, and therefore we need to be able to create valid absolute URLs.

And now it’s time to explain how new windows can be opened with JavaScript code, without any HTML links. Many web pages are doing this, often for popup window with advertising banners, but also sometimes for important stuff. So we might be able to deal with this as well.

In JavaScript there’s the call

window.open(url,target,parameters)

Where “url” is the URL to open, “target” is the target, similar to the target attribute of HTML links and “parameters” are paramters which tell the browser the location and size of the new window and if the new window should open with or without toolbars, etc. When calling “window.open()” on the iPhone with a target that is a new window, nothing will happen. This is because the UIWebView object doesn’t support new windows or tabs. So we need to overwrite the the original “window.open()” function of JavaScript so our own code is executed whenever “window.open()” is called. Our own code will then check if the target will open a new window. If this is the case, we create a new URL with the scheme “newtab”, like we’ve done this with the links. And then we also need to explicitly open the new URL, which can be done by assigning the URL to the JavaScript property “location.href“.

function MyIPhoneApp_ModifyWindowOpen() {
    window.open =
            function(url,target,param) {
                if (url && url.length > 0) {
                    if (!target) target = "_blank";
                    if (target == '_blank') {
                        location.href = 'newtab:'+escape(url);
                    } else {
                        location.href = url;
                    }
                }
            }
}

Please note that this code makes certain assumptions: There are no HTML frames used in the web page and the only target names are “_blank”, “_self”, “_top”, “_parent”. Dealing with frames requires some extra work and would make things more complicated. So this should be handled in a later tutorial.

In the delegate method “webViewDidFinishLoad:” we just need to call the JavaScript function “MyIPhoneApp_ModifyWindowOpen()” as well. So we add a new line of code:

- (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()"];
    [webView stringByEvaluatingJavaScriptFromString:@"MyIPhoneApp_ModifyWindowOpen()"];
}

Now, whenever a web page uses the JavaScript call “window.open()“, to open a page in a new window, the delegate method “webView:shouldStartLoadWithRequest:navigationType:” is called, just like it is called when a link is opened. And because we’ve created a new URL with a “newtab” URL scheme when necessary, this is automtatically detected by the code we’ve written before.

What you’ve learned so far? You can write an iPhone app where a web page loaded in an UIWebView object is able to open new tabs, regardless if this is done using HTML links or via JavaScript using “window.open()”. The whole solution is more complicated than on the Mac where the WebView object is able to notify us about new windows or tabs which have to be created. But even if we have to use a mixture of Objective-C and JavaScript code to solve this task, it’s not too difficult.

Final notes:

There are still some details which are not covered by this tutorial. For example web pages can have “frames” and the targets referenced by links and “window.open()” can also address these frames. So you would need some additional code to check if a frame with the target name exists. If there’s a frame with the target name, then do not modify the link, if no frame exists, you should modify the link (using the “newtab” scheme) and treat the target name just like “_blank”.

Also “window.open()” will usually return a reference to the newly created window object so that the JavaScript code can access it later again. This can not be done on the iPhone using the iPhone SDK (at least not without violating the iPhone SDK agreement). But fortunately, most web pages don’t need to access the new windows later, so this limitation is usually not a big deal.

Posted in iPhone & iPod Touch, Programming.

Tagged with , , , .


60 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Alexander Alexander says

    @Frank
    Yes, in case the link target is within the same page, shouldStartLoadWithRequest is not called, because there’s nothing to load. The only action that is triggered by this link is to scroll the page to the new target.

    If you need to catch user actions of these links, you have to install your own onclick handlers which will call your own javascript function whenever the link is clicked.

  2. Frank says

    Thanks for the suggestion!

  3. Alexandr says

    Hello. I wind trick for find window by ‘name’. It need for frames too.

    “try { if(eval(‘window.’+'winName’+’.document’)) { alert(‘YES’); } } catch(e) { alert(‘NO’); }”

  4. Nik says

    HI Alexander,
    Thanks for nice explanation.
    I have one question as we are going to set target of ‘a’ tag to ‘_self’ in ‘webViewDidFinishLoad’ delegate of UIWebView, which will work fine. but if some one select the link before loading of web page or before ‘webViewDidFinishLoad’ gets call, then what should we do to get notification.
    Thanks in advance.

  5. Alexander Alexander says

    @Nik
    You won’t get a notification unless you have changed these links. And the UIWebView will only notify when the page load has started and when it is finished. When it has started, none of the links do already exist, so this is too early to modify any links. And when the UIWebView has notified you that the page load has finished, then you know that all the links exist. For the time in-between you don’t know anything. You could simply use a timer to modify all links every x seconds or so, but this can have side effects, like performance issues and you would probably check the links multiple times when doing this while the page is still loading. You might try to modify the links when the user touches the screen as an alternative when the page has not yet finished loading.

  6. Nik says

    Thanks Alexander for your help.

  7. Yoel says

    Hi Alexander, it’s impressive how you’ve kept this conversation open for such a long time. Nice! I have a question regarding this in your blog:

    “Also “window.open()” will usually return a reference to the newly created window object so that the JavaScript code can access it later again. This can not be done on the iPhone using the iPhone SDK (at least not without violating the iPhone SDK agreement). ”

    I was wondering if anything have change about that since the time you wrote the blog. The web content I want to display really needs this feature and as you rightly pointed out, since we are opening the new tab manually the reference is lost. Is there anything you can think of that we can do?

    Thanks

  8. Alexander Alexander says

    @Yoel
    Unfortunately, nothing has changed. This issue is still unsolved.

    Apple has changed a few things since the last time, so normal links which would normally open new windows are no longer completely ignored, UIWebView will now just open them normally. But of course, these do not open in a new Tab because the UIWebView API still has nothing that can deal with windows or tabs. For simple Apps which do not open multiple Tabs, things are much easier now. But if you have to support multiple Tabs, nothing has really changed. You still need JavaScript helper code to open links in new tabs, and the popup window issue does still exist.

  9. Snehal Mehta says

    Really cool tutorial. However , I want to know does this mechanism work for popups which has child parent relationship between window objects? Thanks.

  10. Alexander Alexander says

    @Snehal Mehta
    No, I’m sorry. As far as I know, there’s no “legal” way to get the child-parent-relationship working on the iOS platform. This would require to create a valid reference to the popup window for the JavaScript engine, and the iOS does not provide any API for this.

    If someone has a solution for this (which doesn’t require to use private API), I would be interested ;-)

1 2



Some HTML is OK

or, reply to this post via trackback.