Search and highlight text in UIWebView

Several iPhone Apps (like my “iCab Mobile” or “NewsTap” Apps) provide a search feature which allows to search for text in the content that is currently displayed within a UIWebView. The found occurrences of the searched text are highlighted with a yellow background, so the search result can be visually located very easy.

This blog post describes how this can be implemented. I’m implementing this feature as a category for the UIWebView class, so you can use the new search feature for all UIWebView objects in your Apps very easily.

First of all, UIWebView doesn’t allow us to access its content directly, so we have to use JavaScript again. But if you’ve read my other blog posts, you already know this approach.

Our goal is to implement two methods for our new UIWebView category. One method should start the search and highlights the search results. As a result this method should return the number of occurrences of the text we’ve searched. The other method should remove all the highlighted search results again to restore the original layout of the web page.

What we need to do is to write the main code in JavaScript and a wrapper code in Objective C which is simply calling the JavaScript code.

We start with the JavaScript code that is doing the real work. As I’ve already described in the blog post WebKit on the iPhone, the JavaScript code will be saved as resource file in the XCode project. This way it can be loaded from within the Objective C code from the application bundle very easily, and we don’t mix the code of multiple programming languages (JavaScript and Objective C) in the same files.

The following code is the the JavaScript implementation; below I’ll explain what it is doing and how it works:


// We're using a global variable to store the number of occurrences
var MyApp_SearchResultCount = 0;

// helper function, recursively searches in elements and their child nodes
function MyApp_HighlightAllOccurencesOfStringForElement(element,keyword) {
  if (element) {
    if (element.nodeType == 3) {        // Text node
      while (true) {
        var value = element.nodeValue;  // Search for keyword in text node
        var idx = value.toLowerCase().indexOf(keyword);

        if (idx < 0) break;             // not found, abort

        var span = document.createElement("span");
        var text = document.createTextNode(value.substr(idx,keyword.length));
        text = document.createTextNode(value.substr(idx+keyword.length));
        element.deleteData(idx, value.length - idx);
        var next = element.nextSibling;
        element.parentNode.insertBefore(span, next);
        element.parentNode.insertBefore(text, next);
        element = text;
        MyApp_SearchResultCount++;	// update the counter
    } else if (element.nodeType == 1) { // Element node
      if ( != "none" && element.nodeName.toLowerCase() != 'select') {
        for (var i=element.childNodes.length-1; i>=0; i--) {

// the main entry point to start the search
function MyApp_HighlightAllOccurencesOfString(keyword) {
  MyApp_HighlightAllOccurencesOfStringForElement(document.body, keyword.toLowerCase());

// helper function, recursively removes the highlights in elements and their childs
function MyApp_RemoveAllHighlightsForElement(element) {
  if (element) {
    if (element.nodeType == 1) {
      if (element.getAttribute("class") == "MyAppHighlight") {
        var text = element.removeChild(element.firstChild);
        return true;
      } else {
        var normalize = false;
        for (var i=element.childNodes.length-1; i>=0; i--) {
          if (MyApp_RemoveAllHighlightsForElement(element.childNodes[i])) {
            normalize = true;
        if (normalize) {
  return false;

// the main entry point to remove the highlights
function MyApp_RemoveAllHighlights() {
  MyApp_SearchResultCount = 0;

The basic principle of searching the text and removing the highlighted search results is the same: We’re working at DOM level (Document Object Model), which means the HTML document is represented as a tree structure where each HTML element, text, comment etc. is represented as a node. All the nodes are linked together with parent and child connections. The root element of each HTML document is the element that is created by the HTML tag. This element has usually two children: The HEAD element and the BODY element. Only the content of the BODY element is visible and displayed on screen, so we only need to process this part of the document tree.

What we need to do is to start with the body element and traverse all of its child nodes. From within the child nodes we need to go to their child nodes as well, and so on until we reach a leaf nodes, which has no child elements. Text nodes are always leaf nodes and text nodes are the nodes which might contain the text we’re looking for.

Traversing the whole HTML tree searching for all text nodes can be done by a recursive algorithm called Depth-First-Search (DFS). The DFS algorithm will traverse the tree structure starting from a root element (in our case the BODY element) to the first leaf node in a branch of the tree (for example going to the first child of the root first, from there again going to the first child, etc until a leaf node is reached). Then the algorithm goes back (backtracking) to the last node where not all child nodes were traversed yet and continues with the next unvisited child nodes etc. This way all nodes of the whole tree are traversed and we are able to find all the text nodes in which we are looking for the text we are earching.

The functions “MyApp_HighlightAllOccurencesOfStringForElement(element,keyword)” and “MyApp_RemoveAllHighlightsForElement(element)” are both implementations of this DFS algorithm. These functions are called from MyApp_HighlightAllOccurencesOfString(keyword)” and “MyApp_RemoveAllHighlights()” which are doing the necessary initialization and provide the DFS functions with the proper root element (the BODY element). The initialization for a new search is to make sure than no highlighted text from a previous search is present, so we simple call the function to remove all text highlights.

When searching for a text, we check if the currently inspected node is a text node or if it is an element node. If it is an element node it can have child nodes, and these must be inspected as well. If it is a text node, we have to find out if the text of this node contains the text we’re searching. If yes, we insert the text highlight, otherwise we are finished with this node. Also if the node is neither a text node nor an element node, there aren’t any child nodes which are interesting for us, so we are finished with this node as well.

When the text of a text node contains the searched text, we have to split the text into three parts. Part one will contain the text up to the searched text, part two contains the searched text itself and part three contains the rest of the text. A new element will be created (a SPAN element) and the second part (the searched text) will become a child node of this new element. Now we can assign StyleSheet rules to the newly created SPAN element to create the highlight effect (setting the background color to yellow, setting the text color to black, you can even increase the font size, if you want to). Now the new element is linked with part one and three so it becomes a part of the tree strucuture of the HTML tree. And because the searched text might be found multiple times in the original text node, we continue to search for the searched text in the third part of the original text node. If we find another occurrence of the searched text, we split this third part again in three parts, otherwise we are finished. When we create a SPAN element for the highlight effect, we also assign a special value (here “MyAppHighlight”) for the class attribute. This is important to be able to find these elements later again when we want to remove the highlight effects using the function “MyApp_RemoveAllHighlights()”. For this task we traverse the tree as well, but now we’re looking for elements whose class attribute has this special value. To restore the original state of the HTML document, we have to remove the elements we’ve inserted before (the ones with the special value of the class attribute) and we need to concatenate the text node we’ve split. JavaScript can help us to concatenate the text nodes again, because it provides the “normalize()” function which can do this for us.

In JavaScript we can find out the type of a node with the “nodeType” property. A value of 1 means that the node is a normal element node (like the “body” node, a “span” node etc.). A value of 3 means that the node is a text node. In this case the property nodeValue contains the text of the node. Other values for nodeType represent comment nodes (in HTML these are written as “<!– Comment –>”), attribute nodes (for HTML attributes like for example the “HREF” attribute for the “A” tag), document nodes and some more. In our case only the values 1 (element node) and 3 (text node) are important.

In the above implementation, we count the number of found occurrences in a global variable.

Note: You’ll notice that the JavaScript function names and variables and also the value for the class attribute I’m using in the above code are very lengthy and they do also have a prefix like “MyApp_”. The reason for this is to avoid any conflicts with existing function and variable names of the web page in which we inject our JavaScript code. If you’re generating the HTML code yourself that is displayed in the UIWebView object, you can choose shorter and simpler names. But if you have to deal with HTML and JavaScript code of any web pages (like in a web browser like iCab Mobile), you should use longer names and also add the name of your app as Prefix to all function and variable names to avoid any conflicts.

The Cocoa/Objective C part of the implementation is very simple. We only need to declare the interface and write a simple wrapper which loads and calls the JavaScript code that is actually doing all the hard work. The interface is also simple, we only need two methods: one to start the search and which highlights the found text and one which removes all the highlights again.


@interface UIWebView (SearchWebView)

- (NSInteger)highlightAllOccurencesOfString:(NSString*)str;
- (void)removeAllHighlights;


The typical use case would be to provide a search field where the user can enter some text. This text would be passed to the method “highlightAllOccurencesOfString:”. And when the user shakes the device, the App could call the method “removeAllHighlights” to remove all the highlighted search results again.

The implementation would look like this:


@implementation UIWebView (SearchWebView)

- (NSInteger)highlightAllOccurencesOfString:(NSString*)str
    NSString *path = [[NSBundle mainBundle] pathForResource:@"SearchWebView" ofType:@"js"];
    NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    [self stringByEvaluatingJavaScriptFromString:jsCode];

    NSString *startSearch = [NSString stringWithFormat:@"MyApp_HighlightAllOccurencesOfString('%@')",str];
    [self stringByEvaluatingJavaScriptFromString:startSearch];

    NSString *result = [self stringByEvaluatingJavaScriptFromString:@"MyApp_SearchResultCount"];
    return [result integerValue];

- (void)removeAllHighlights
    [self stringByEvaluatingJavaScriptFromString:@"MyApp_RemoveAllHighlights()"];


The first thing we’re doing in the method “highlightAllOccurencesOfString:” is to load the JavaScript file we’ve written above from the application bundle and inject it into the web page that is currently displayed in UIWebView. Because we’re implementing this as a category for UIWebView, we can use “self” to call the method “stringByEvaluatingJavaScriptFromString:” of the UIWebView instances.

After we’ve injected the JavaScript code we simply call the JavaScript function we’ve defined above to do the search.

And finally we access the variable we’ve defined in the JavaScript code, which represents the number of occurrences of the string that were found, and we return its integer value as the result of the method.

In the method “removeAllHighlights” we only need to call the corresponding JavaScript function we’ve defined in the JavaScript code from above. Loading the external JavaScript file and injecting it into the Web page is not necessary here. If we’ve started a search before, the code is already injected and we don’t need to do this again. And if we haven’t started a search before, we just don’t need the JavaScript code because there are no highlights which have to be removed.

As you can see, the Objective C code for the UIWebView category is just a simple wrapper code for the JavaScript code. Now, when you have a UIWebView object in your App, you can simply search for text in its content by calling “highlightAllOccurencesOfString:”, like in this example where we’re searching for the text “keyword”:

  // webView is an instance of UIWebView
  [webView highlightAllOccurencesOfString:@"keyword"];

Additional notes: In case your App has to deal with web pages which can have frames, you have to add some additional code that looks for frames. You have to traverse all the documents of all the frames to find all the text nodes in all frames. The code from above isn’t doing this to keep it as simple as possible.

322 thoughts on “Search and highlight text in UIWebView

  1. Hi Alex,
    This tutorial is really very helpful & searched worked like a charm. But I have few queries.
    Can we achieve following things in HTML book using javascript?

    1. Can I store the location of each result of searched text?
    2. Can I highlight, underline, strikethrough text inside HTML & store their location as well?
    I want to store the location because when Application restarts I would be reapplying those annotations (highlight, underline, strikethrough)

  2. @Amey Gadre
    First of all, you need to be aware that web sites do change from time to time, so you have to be prepared that your saved locations could become invalid any time.

    Also you can not store references to site elements outside of the context of the web site easily, because the next time you load the page, these references will be different. But assuming you can detect that a page has changed (for example by checking the last modification date in the HTTP headers, the content size etc), you can simply count the number of occurrences of the search term and the index of the locations which you want to highlight. When you load the page later again, do the search again, count the occurrences again and then it should be easy to highlight the correct page elements again.

    The way you highlight anything is up to you. In the above example I only changed the background color, but you can apply any HTML or CSS layout property here, so you can also underline and strikethrough.

  3. Thank for this post, it works only in html file , But I have to highlight on pdf file, please let me know any other solution . that will be great.

  4. @Ravi
    Unfortunately there’s no solution for PDF files. The only way the web view allows manipulation of the content is this one „stringByEvaluatingJavaScriptFromString“ call, which relies on executing Javascript. And this only works for HTML but not for PDF.

  5. Thanks for the post Alexander, this JavaScript Code is designed for UIWebView i suppose. But i need to search and highlight text in WKWebView, do you have any information about that?

  6. For WKWebView this works more or less the same way. The only difference is that instead of the method „stringByEvaluatingJavaScriptFromString:“ or UIWebView, you need to call the method “evaluateJavaScript:completionHandler:“ of WKWebView.
    You can pass the same JavaScript code to this method, and you get the result back in the completion handler.

  7. Thank you for quick respond Alexander. It works as expected, thank you for this great tutorial.

  8. Hi Alex,
    This tutorial is really very helpful & searched worked smoothly for me, but I am facing one issue, as I have to move from one string to another, so I have implemented next and previous methods, where I am facing issue in:
    function MyApp_HighlightAllOccurencesOfStringForElement(element,keyword)
    else if (element.nodeType == 1) { // Element node
    if ( != “none” && element.nodeName.toLowerCase() != ‘select’) {
    for (var i=element.childNodes.length-1; i>=0; i–) {
    Searching is not symmetric in para, when I move to previous and next string, it firsts jump at last string in para then at first.

  9. @Yashika
    I’m not sure what exactly you’re doing within your next/previous methods. But the „highlighting“ method you’ve mentioned does only highlight all occurrences of the search term, so it does not need to take the order into account. And because highlighting requires to insert additional HTML elements, the number of child elements can go up. Therefore the highlighting method modifies the HTML code backwards, going from the end to the beginning, this way the newly added elements won’t be visited again when traversing the HTML tree.
    In order to go back and forward you probably better create an array of the highlighted elements within the highlighting method from above. So you get a list of all occurrences, and when going back or forward you only need to decrement or increment an index which represents the current element from the array, and then make the background of the previously selected item transparent and the background of the new element yellow for example.

  10. @Yashika
    I guess your problem is that using the next/previous buttons seems to highlight „random“ occurrences, not always the one you would expect.

    To solve this issue, it’s probably best to draw the HTML code as tree and try to understand how and in which order the tree is traversed and when creating new SPAN nodes for the highlight, how exactly text nodes are split into pieces and in which order these are inserted in the tree. This is important because you enumerate the created SPAN nodes using the „ID“ attribute, so you can address them with the next/previous buttons. You really need to understand how exactly the HTML tree is modified in order to lear why you get the „random“ results.

    But I guess the easiest solution to your problem would be to solve the enumeration (for the ID attribute) in a completely separate step. So in the first step do what the scrip from this blog post is doing: find and mark all occurrences with a default color. And in a second step traverse the HTML tree again, this time without doing any modifications of the tree structure and from the beginning to the end. So when doing this in depth-first-order, you would find the SPANs with the occurrences in the correct order, so if you set the ID attribute now, they should be in the correct order and you should be able to avoid the randomness when going back or forward.

  11. Awesome. I used this combined with a navigation through the found items by using an array like you said. Thanks a lot.

  12. Thank for this post, it works only in html file , But I have to highlight on pdf file, please let me know any other solution . that will be great.

  13. @liyanjun
    Unfortunately there’s no easy solution available for PDF file. The PDF files are loaded in a special PDF-View within the UIWebView, which is not accessible via JavaScript. So there’s no public way to access the content of the PDF in an UIWebView object.

  14. Thank you for such a wonderful tutorial. It helped me lot. I want to scroll web view to the first occurrence of matched string. Is it possible? Thanks in advance.

  15. @Yogita
    Yes, this is possible. If you have an HTML element (like a SPAN element) which should be visible, you can use the JavaScript method “scrollIntoViewIfNeeded()“ to scroll this element into view. If „element“ is a reference to the SPAN element, you can use


    to scroll this element so that it will be visible in the center of the screen. If you pass „false“ as parameter, it will be scrolled so that it will be near the top or bottom screen area, depending of the edge that is closest to the element.

  16. Thanks for your idea and sample code. It still works perfectly in 2018. Happy New Year 🙂

  17. Sorry to resurrect a post so old, but… I came across your tutorial as seemingly the only function out there that seems to work with WKWebView (as per your previous comments). The only issue I have is that my whole app is written in Swift 4.2, and I’m not familiar with Objective-C.

    As well as adjusting for WKWebView as opposed to UIWebView – can this be used in a Swift application, if so, where should I start?

    Thanks in advance

  18. I am able to highlight a single string by double-tapping on the word and highlighting the string using javascript. Now, I want to show the previously highlighted words by default when the web view loads the next time. Any idea how can I achieve it?

  19. @Muneeb
    Basically all you need to do is to find and save the selected text the first time. And later you can use the „search“ feature from this blog post to highlight the same text again.

    To find the currently selected text, you can use the JavaScript code:
    text = window.getSelection().toString()

    And you can use the method „stringByEvaluatingJavaScriptFromString“ to read the selected text from the web site from within your App.

Leave a Reply

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