Oct 30

For a recent project I encountered the following requirements:

  1. Download and store HTML documents that are rendered using the UIWebView class.
  2. Download and store the external documents referenced in the HTML, like images, so that they, along with the HTML documents, are available in non-networked situations.
  3. Use the same HTML documents and external documents as the body of in-app generated Emails.

The challenge was to devise a scheme whereby the same HTML documents can be used for both rendering content within the app under non-networked situations and to provide the exact same user experience within the Email body; a clear Catch-22!

There are a number of possible approaches (see page 10 of Nick’s presentation, but the one we choose was to use complete URL’s for all externally referenced documents within the HTML (for example, all images are referenced on a CDN). We choose this route as it provides the simplest way to support the Email requirement.

Next we had to figure out a way to modify these URL’s dynamically to reference the same external documents located locally on the device. To do this we use some features of UIWebView and Javascript.

First we set up the UIWebView and implement the webViewDidFinishLoad delegate method to kick off the Javascript. Below you can see we take advantage of the webview’s stringByEvaluatingJavaScriptFromString() method to call our Javascript event listener useLocalPaths().

- (void)webViewDidFinishLoad:(UIWebView *)webView {
  DLog(@"Local URLs used = %@",[webView stringByEvaluatingJavaScriptFromString: @"window.useLocalPaths()"]);
}

The Javascript for the event listener is the following:

<script type="text/javascript">
  window.addEventListener('load',function() {
    window.useLocalPaths = function() {
      var images = document.getElementsByTagName('img');
      for (var i=0; i<images.length; i++) {
        var url = images[i].src;
        if (url.length>0) {
          var bits = url.split('/');
          images[i].src = bits[bits.length-1];
        }
      }
      return true;
    };
  },false);
</script>

In this case, this event listener queries for all image tags and modifies the src attribute by stripping all of the URL except for the image file name. For example if our image tag looked like this,

<img src="http://www.pervasentcdn.com/pervasent.jpg" class="logo" alt="Pervasent" />

it would be come this

<img src="pervasent.jpg" class="logo" alt="Pervasent" />

and thus achieves our goal.

It’s worth noting that the general approach of using UIWebView + Javascript is quite handy and obviously has many possible applications.

Update: If you’re having timing issues with this approach, you may want to take a look at this technique: How To Inject JavaScript Functions Into A UIWebView

written by Jess \\ tags: ,

8 Responses to “UIWebView: Dynamically Modify HTML documents”

  1. Clive Says:

    can this technique also be used to change references to css & js files to local versions? and change refs to images/css/js within the css and js files?

    I wonder how to completely download a web page to view offline? Would the sequence be:
    1. parse the html for css, js and images
    2. change references to local ones
    3. build an array of each reference
    4. download each item in array to local file
    5. parse each local css and js files for more references
    6. change references to local ones, build array and download. goto 5 till all files are done.

    or something like that

  2. Nick Says:

    @Clive: Yes, that would be the process. However parsing the HTML, CSS and JavaScript for all remote references is not a trivial task. An alternative is to use the shouldStartLoadWithRequest method of UIWebView to gather the information about remote references.

  3. Pat Says:

    Isn’t this what the <base href=””> tag is for? Haven’t tried it so maybe it doesn’t work in this case.

  4. Nick Says:

    @Pat: That is a good question. I recall testing the base tag with UIWebView a long time ago, without much success. Time to give it another try and see if I can get it to work, or document why it doesn’t.

  5. Riskpp Says:

    Could you please explain how to gather information about remote references using shouldStartLoadWithRequest method?
    thanks!

  6. Nick Says:

    @Riskpp: My comment above was in error. shouldStartLoadWithRequest does not see each request made as external resources are loaded for an HTML page in an UIWebView.

  7. ashish Says:

    any help on this, the thread with same question but not any proper answer
    http://stackoverflow.com/questions/2707210/uiwebview-paging-line-cut-off.

  8. Manraj Says:

    I have an requirement:
    1. Loading html files always from server. So I have to use [webview loadRequest] and cannot use loadHTMLString:baseURL and loadData:MIMEType:textEncodingName:baseURL:.
    2. I have saved some images for html file in document path.
    3. By using stringByEvaluatingJavaScriptFromString:, i have passed document image file path to the JS method.
    In JS:
    function setLocalFilePathUrl(elementId, localFileUrl)
    {
    var image = document.getElementById(elementId);
    if (image && localFileUrl)
    {
    image.src = “file:/” + localFileUrl;
    alert(“image.src: ” + image.src);
    }
    }

    The above method does not load and display the image. Alerted image.src is correct compared with below htmlText.

    4. NSString *htmlText = [NSString stringWithFormat:@””, localFileUrl];

    If i load the htmlText with loadHTMLString:htmlText baseURL:nil, it displays the image.
    I have checked with image.src in webViewDidFinishLoad by
    stringByEvaluatingJavaScriptFromString:@”var image = document.getElementById(‘image’); if (image) { alert(image.src); }”

    image.src – alert 3 and 4 are same (matched).

    Basically, i want to change the image url with document path file url via JS in the loaded page with http.

    any one have any idea, is it possible?

    Thank you,
    Manraj

Leave a Reply