VGTech is a blog where the developers and devops of Norways most visited website share code and tricks of the trade… Read more



Are you brilliant? We're hiring. Read more

Dissecting Javascript clicks in UIWebView

iOS

In VG we make heavy use of UIWebView, almost all of our iOS apps is a mix between native code and HTML. This is nice and dandy for presenting all sorts of content – being a newspaper means tons of webpages with ads, frames, javascript menus and whatnot.

Generally this works out really well, but for a long time we had a problem with links intercepted by javascript and how to distinguish these from an iframe loading. Why would we want to do this? Sometimes we wish to load linked pages in a separate view, leaving the originating page intact. Clicking on an article on our front page will push a new webview on the navigation stack so that when you’re done reading the article you can close it and instantly go back to the same spot on the front page. We use webview’s shouldStartLoadWithRequest delegate method for this purpose, like this:

Show code

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

 if(navigationType == UIWebViewNavigationTypeLinkClicked || navigationType == UIWebViewNavigationTypeFormSubmitted) {

      [self pushFrontWithURL:[request URL]];      //this will make a new webview and push it on our navigation controller

      return NO;

}

return YES;

}

 

That’s fine for regular links and forms, but  as soon as you hook up some javascript to your a- tags 

Show code
<a href="http://www.vg.no/test" onclick="myJsFunc();">
    Run JavaScript Code
</a>

they will be ignorered by our code.

The problem is the navigationType, which has 6 possible values:

Show code

typedef NS_ENUM(NSInteger, UIWebViewNavigationType) {

    UIWebViewNavigationTypeLinkClicked,

    UIWebViewNavigationTypeFormSubmitted,

    UIWebViewNavigationTypeBackForward,

    UIWebViewNavigationTypeReload,

    UIWebViewNavigationTypeFormResubmitted,

    UIWebViewNavigationTypeOther

};

When javascript is attached to a link, the navigationType will no longer be reported as UIWebViewNavigationTypeLinkClicked – you will get a UIWebViewNavigationTypeOther instead. So we’ll just check for that! Unfortunately it’s not that easy. Every page load and iframe in your page will trigger this action as well.. How can we separate the javascript-bound links and a regular page load ?

NSURLRequest to the rescue

There are a couple of  methods in NSURLRequest which might seems irrelevant at first, but we’ll use them to serve our purpose.

Show code
/*!

@method URL

@abstract Returns the URL of the receiver.

@result The URL of the receiver.

*/

- (NSURL *)URL;

/*!

@method mainDocumentURL

@abstract The main document URL associated with this load.

@discussion This URL is used for the cookie "same domain as main

document" policy. There may also be other future uses.

@result The main document URL.

*/

- (NSURL *)mainDocumentURL;

All iframes loading will have their URL pointing to a specific address, but the mainDocumentURL will still name our original document, in other words, they differ. But what happens when we click on our javascripted links ? The URL and mainDocumentURL will be the same, if they link to a new page. And that’s exactly what we’re interested in. The only problem that remains is the first page load, which happens to follow the same pattern. There are several ways to work around that, but one quick and easy solution is to check for the inital URL and handle that as a special case. Wrapping it all up you can try something like this:

 

Show code

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

 

NSString* url = [[request URL] absoluteString];

//first page load, don't move away

if( [url isEqualToString:self.frontpageUrlString] ) {

    return YES;

}

if(navigationType == UIWebViewNavigationTypeLinkClicked || navigationType == UIWebViewNavigationTypeFormSubmitted) {

    [self pushFrontWithURL:url];      //this will make a new webview and push it on our navigation controller

    return NO;

}

 

//push our own javascript-triggered links as well

else if(navigationType == UIWebViewNavigationTypeOther) {

    NSString* documentURL = [[request mainDocumentURL] absoluteString];

    if( [url isEqualToString:documentURL]) {             //if they are the same this is a javascript href click

        [self pushFrontWithLink:url];

        return NO;

    }

}

return YES;

}

 

You still might want to check for other URLs, such as links to Appstore and other apps and let iOS handle those, but your javascript links should now behave nicely. Happy hybrid coding!

iOS Developer at VG Mobil


5 comments

  • Yvan

    That was Excellent. Thanks!!


  • Mike Morawski

    Thanks for sharing!


  • Patrick Greene

    Needed this thank you very much!


  • Paul Dunn

    Thanks very much. I'm so glad I found your tip. My problem was slightly different in that I've been trying to catch the javascript clicks in embedded social media posts from Twitter and Instagram. I noticed that the URL and mainDocumentURL were often the same for UIWebViewNavigationTypeOther so just checking those wasn't enough. I tested for 'http' as well and this seems to differentiate it enough to work.


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

    NSString * url = [[request URL] absoluteString];
    NSString * documentURL = [[request mainDocumentURL] absoluteString ];

    NSString * httpCheck = [url substringToIndex:4];

    if ( (navigationType == UIWebViewNavigationTypeLinkClicked)
    || (([url isEqualToString:documentURL]) && ([httpCheck isEqualToString:@"http"])) ) {

    [[UIApplication sharedApplication] openURL:[request URL]];

    return NO;
    }

    return YES;
    }


  • Karina

    Hi, I have a question about this code

    //first page load, don't move away

    if( [url isEqualToString:self.frontpageUrlString] ) {

    return YES;

    }

    Can you explain me what this mean, I'm using swift. I really appreciate your help on this.

    Thanks!


Leave your comment