Skip to content


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:

SearchWebView.js:

// 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));
        span.appendChild(text);
        span.setAttribute("class","MyAppHighlight");
        span.style.backgroundColor="yellow";
        span.style.color="black";
        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 (element.style.display != "none" && element.nodeName.toLowerCase() != 'select') {
        for (var i=element.childNodes.length-1; i>=0; i--) {
          MyApp_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword);
        }
      }
    }
  }
}

// the main entry point to start the search
function MyApp_HighlightAllOccurencesOfString(keyword) {
  MyApp_RemoveAllHighlights();
  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);
        element.parentNode.insertBefore(text,element);
        element.parentNode.removeChild(element);
        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) {
          element.normalize();
        }
      }
    }
  }
  return false;
}

// the main entry point to remove the highlights
function MyApp_RemoveAllHighlights() {
  MyApp_SearchResultCount = 0;
  MyApp_RemoveAllHighlightsForElement(document.body);
}

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.

SearchWebView.h:

@interface UIWebView (SearchWebView)

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

@end

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:

SearchWebView.m:

@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()"];
}

@end

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.

Posted in iPhone & iPod Touch, Programming.

Tagged with , , , , .


300 Responses

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

  1. xiang.gao says

    hi,Alexander
    I want to let the UIWebView scroll to the highlight position, I add the follow functions to the
    SearchWebView.js,but it doesn’t work well.Can you help me to check if there is anything wrong?

    function MyApp_HighlightMoveToIndexOfString(keyword,index) {
    MyApp_HighlightMoveToIndexOfStringForElement(document.body, keyword.toLowerCase(),index);
    }
    function MyApp_HighlightMoveToIndexOfStringForElement(element,keyword,index) {
    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; i–) {
    MyApp_HighlightMoveToIndexOfStringForElement(element.childNodes[i],keyword,index);
    }
    }
    }
    }
    }

  2. Alexander Alexander says

    @xiang.gao
    Yes, I can see that this code won’t work well. You’ve created a loop which either never exits or which is aborted because of an exception. There are several indexes you’re using in your code (index, idx, i) but they won’t be used properly. For example in the line “if (idx = 0; i–)” the “idx = 0″ is an assignment operation not a comparison operation ( “idx >= 0″), and where comes the “i” from? And what is “index” used for? Also a text node doesn’t have child nodes.

  3. xiang.gao says

    hi,Alexander
    Thank you for your reply.
    I sorry that I copied the wrong souce ,my souce is the follows:

    About the parameter index,if the index’s value is 3,It means that let the UIWebView scroll the third highlight place.

    function MyApp_HighlightMoveToIndexOfString(keyword,index) {
    MyApp_HighlightMoveToIndexOfStringForElement(document.body, keyword.toLowerCase(),index);
    }

    function MyApp_HighlightMoveToIndexOfStringForElement(element,keyword,index) {
    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; i–) {
    MyApp_HighlightMoveToIndexOfStringForElement(element.childNodes[i],keyword,index);
    }
    }
    }
    }
    }

  4. Alexander Alexander says

    @xiang.gao
    It seems that you’ve posted the same code again.

    Please have a look at this code. Your code never scrolls anything, and you’re never doing anything with the parameter “index”.

  5. Charmi says

    Alexander,
    First of all thanks for the nice tutorial,but as i am just a beginner with programming,i am too confused about how to implement that thing.I created file named SearchWebView.h/.m and also SearchWebView.js but now i am confused with how to implement that. I would like to include that i am creating one pdf based application and wanted to highlight the text using this function on my one button event. Can you help me please? Thanking you.

  6. Alexander Alexander says

    @ Charmi
    You can’t use this approach for PDFs. It will only work for HTML documents.
    The above code uses HTML/DOM methods to search for the text and will create new HTML elements to highlight the search results. This can not work with PDF.

    I’m sorry, but for PDF you probably need to read and parse the PDF files yourself.

  7. yama says

    Hi Alexander,
    Thank you for the tutorial.
    But while implementing it,i am able to fetch path using :
    NSString *path = [[NSBundle mainBundle] pathForResource:@”SearchWebView” ofType:@”js”];
    but i am getting jscode as null with following line :
    NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];

    Can you help me with solution like where i am making mistake?

  8. xiang.gao says

    hi Alexander,
    I want to use “window.scrollTo(x,y) to move to the position of highlight text,but I don’t know how to get the position of the highlight text.
    How can I get that?

  9. Alexander Alexander says

    @yama
    Make sure the “js” file is added to your XCode project as “resource” file. By default XCode will treat this as “compilable” source code and will try to compile and link the JS file instead of copying the file into App package. And then the file can not be loaded.

    To fix this, look into the sidebar of the project window in XCode and locate the section “targets”. Here you should move the .js file from “compile source” to “copy bundle resources” section.

  10. Alexander Alexander says

    @xiang.gao
    To calculate the coordinates of an HTML element you have to sum up the offsetLeft (and offsetTop) properties of all elements, started from the element itself up to the root element of the HTML tree, by traversing the HTML tree to the root using the offsetParent properties.

    This would look like this:

    The variable node is the element you want to calculate the coordinates for:

    	var x = 0; 
    	var y = 0;
    	var width = node.offsetWidth;
    	var height = node.offsetHeight;
    	do {
    		x += node.offsetLeft;
    		y += node.offsetTop;
    	} while (node = node.offsetParent);
    

    Now x,y width and height do contain the coordinates and dimension of the element.

  11. xiang.gao says

    hi,Alexander
    I try the method to get the position of the highlight text.But it doesn’t work.
    My page has about 10 palces highlight text,but of 3 of these get the right position,the others get the position (0,0).

  12. Alexander Alexander says

    @xiang.gao
    Can you send me a test project where I can check this myself?

  13. loosy says

    hi ALexander
    Your tutorials are very helpful in learning about UIWebView+JavaScript and I’m now started following you.

    I’ve a question to you about text selection:
    I want to highlight the text on selection by the user how can I do for this in UIWebView.

    Any help will be appreciated.

  14. xiang.gao says

    hi Alexander
    Thank you very much.
    I can send you a test project,what’s your email address?
    (my email is flyhigh209@gmail.com)

  15. Alexander Alexander says

    @xiang.gao
    Just use alexander@icab.de

  16. Alexander Alexander says

    @loosy
    What exactly do you want to do? If the user selects text, it is already highlighted by the iOS in blue and there are some handles to move the start and end point of the selection. The way the iOS highlights selected text can not be changed, as far as I know.

    If you want to add any highlighting on HTML level, then you can do this with JavaScript. There’s nothing special UIWebView-related required. But this can become complicated, because the text selection doesn’t need to start and end at HTML element “boundaries”. So you may need to re-structure parts of the HTML tree to be able to add your styles to just the selected text.

    If you only want to get the selected text out of the UIWebView so you can process it in your Cocoa code, you can use the JavaScript function “window.getSelection()”. This returns the selected text as string. And with the method stringByEvaluatingJavaScriptFromString: of the UIWebView object, you can get the selected text as Cocoa string.

  17. loosy says

    @Alexander
    let me provide some more detail of my problem.

    Just to provide annotation I want to highlight the specific text (provided by user) and get the coordinates to make rectangle to provide focus on it.

    Instead of searching word as this tutorial provides I want to search a snippet of text highlight it and get text coordinates to draw rectangle.

    I think its now more clear.

    Can you help me to overcome this problem.

  18. Alexander Alexander says

    @loosy
    Getting the coordinates of the selected text isn’t always that easy, because the text selection isn’t always a rectangle. For example when the text selection starts in the middle of a line and ends in the middle of another line, you get a set of rectangles or a set of coordinates for the selection. Also if the text spans multiple sections, table cells etc. you get a set of rectangles or coordinates. So the whole task can be complicated.

    I haven’t done this yet, but using the method from one of my comments above, you can get the coordinates of a certain HTML element (the problem that was described above where the coordinates were returned as (0/0) was caused by invisible or hidden text, which was also found). So you could calculate the coordinates of the highlighted elements and collect them in an array before you draw these rectangles….

  19. Kunal P. Kansara says

    Thanks, It works fine….

  20. tiantian says

    I wander if there is a way to clear text selection in UIWebView?

  21. Alexander Alexander says

    @tiantian
    If you mean the text selection that was created by the user for Copy & Paste command, then I think the user has to delete the text selection himself. I guess the selection will also go away if you make another the first Responder or maybe also if you remove the first reponder status from the web view. But I haven’t tried this yet.

  22. Camilla Egelund says

    Hi Alexander,

    A really great tutorial you have made :)
    I was just wondering if you could help me figure something out.
    I have made this iPhone application where I would like to load a webpage in a UIWebView inside my app. I am only interested in this one specific webpage that contains some javascript that I do not understand how to load/activate.
    Let me just give you little piece of the html code from the webpage:

    <a href=”WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;SelectTimeBtn&quot;, &quot;&quot;, false, &quot;&quot;, &quot;SelectTime.aspx?BusinessId=3&Mode=SelectTime&prodId=55&quot;, false, true))” rel=”nofollow”>Ved at vælge et bestemt tidspunkt</a>

    How do I sort of “capture” this href attribute so I can handle it in my Objective C code?

    Thanks in advance, please let me know if I should explain myself better :)

  23. Alexander Alexander says

    @Camilla Egelund
    Are you sure that this snippet is really complete. The HREF attribute must contain a URL, but in this snippet, it does not contain a valid URL. It looks like a “javascript” URL, but then the URL scheme specifier would be “javascript:”. Without the scheme specifier the browser would use the URL scheme of the web page itself (which would be “http” in most cases) and then the URL would be still invalid.

    But if you simply like to “simulate” a click on the link from within your ObjectiveC code, you only need to use the value of the HREF attribute and call it using the method “stringByEvaluatingJavaScriptFromString:” of the UIWebView class. Just replace the “&quot;” by single quote characters and “&amp;” by “&” in your Objective C code (the HTML entities “&something;” are only valid within HTML code so they have to be replaced).

  24. vivek says

    Hi,

    I have some urgent requirement. I want to extract some data from HTML file based on inputs. Let’s say, i have input 1. now, i want to load only the data related to header one. I have input 2. now, i should load data related to header 2. Something like this.

    Can you please help me out? I need it urgently.

    Thanks in advance for your help.

  25. Alexander Alexander says

    @vivek
    It’s difficult to help based on a vague description. I think you should look for a JavaScript and DOM tutorial to learn about how to traverse the HTML tree and access the HTML elements to find the information you need.

  26. Peter says

    Be aware although I personally think this is a nice approach it does not pass the Apple App
    Approval Guidelines :

    ———————————————————————————————————————-
    2.17 Apps that browse the web must use iOS Webkit framework and Webkit Javascript.
    We have included additional details below to help explain the issue and hope you’ll consider revising and resubmitting your application.

    iOS WebKit framework and WebKit Javascript must be utilized in applications that browse the web, and native Webkit rendering or javascript behavior cannot be modified.

    To utilize iOS Webkit or Webkit javascript, please refer to the UIWebView Class Reference
    ———————————————————————————————————————-

    Not quite sure (yet) what this means but the UIWebView Class Reference does state ‘The UIWebView class should not be subclassed’.

    Would be interested to hear any thoughts people may have !

  27. Alexander Alexander says

    @Peter
    What exactly do you think does not match the guidelines?

    I do not subclass UIWebView, and adding a “category” to UIWebView is not subclassing. And the JavaScript routines I use to overcome the limitations and restrictions of UIWebView are just using the existing WebKit framework and WebKit JavaScript with its public API. The native WebKit rendering or javascript behavior is not modified.

    And if you’ve received this with an App rejection, are you sure that this refers to the search feature?

  28. Peter says

    Thanks for your prompt reply – BTW love your post – Learnt alot of useful things here !

    My App is a simple Web Browser with a interface that allows the user to highlight
    dictionaries of words.

    Your question is a interesting one ….

    Apple does not give you much of a clue – the key portion of the Rejection is :
    ——————————————————————————————————————–
    We’ve completed the review of your app but cannot post this version to the App Store because it is not appropriately using the iOS Webkit framework as a web browser. Applications that browse the web must utilize the iOS Webkit framework, as stated in the App Review Guidelines :

    2.17 Apps that browse the web must use iOS Webkit framework and Webkit Javascript.

    We have included additional details below to help explain the issue and hope you’ll consider revising and resubmitting your application.

    iOS WebKit framework and WebKit Javascript must be utilized in applications that browse the web, and native Webkit rendering or javascript behavior cannot be modified.

    To utilize iOS Webkit or Webkit javascript, please refer to the UIWebView Class Reference: .
    ——————————————————————————————————————–

    Having read the Guidelines (and related posts on the web) the only reasons I can think of are as follows :
    1. Adding a ‘category’, I understand your point on subclassing but maybe Apple also think that this also should not be done ?
    2. Loading Javascript into the UIWebView – Only real ‘reason’ I can think of for rejection.

    I am going to attempt to re-write the app without the loading of Javascript and then re-submit.

    I would appreciate your thoughts.

    Regards
    Peter

  29. Alexander Alexander says

    @Peter
    Maybe one of the review guys just mixed up something or didn’t understand the API and therefore didn’t know that what you’ve done can be done without violating the guidelines?

    Or it’s the GUI of your App which is a problem for the review guy. Maybe the way you’re doing things has confused them?

    Maybe you should ask the review team for more details about the problem, hopefully they tell you more precise what’s the problem.

    A category allows us to write “helper” functions which look like they belong to the original class. In other programming languages without such a “category” concept, you would immediately see that this function does not belong to the original class. The category concept hides this and makes the code much more readable and easier to maintain. This is a key concept of Objective C and Apple can not really disallow writing categories. This would not make any sense.

    Also the only purpose of the method “stringByEvaluatingJavaScriptFromString:” (which is a public method) is to load and execute JavaScript in a UIWebView object. If Apple doesn’t want that we can do this, they have to make this call private (and at the same time they have to remove 100000 Apps from the AppStore, which do use exactly this method…)

  30. Peter says

    Thanks again for your prompt reply.

    As you have suggested I have sent an email to App Review
    asking for clarification – I will let you know the details of the response (here’s hoping).

  31. loosy says

    hi Alexander,

    When I give any keyword it highlights all occurrences but when I give a paragraph or 2 to 3 lines to search it doesn’t highlight.

    Actually I want to search a string that can be one keyword or even a paragraph. And I want to highlight its first occurrences.

    Any help will be appreciated.

  32. Alexander Alexander says

    @loosy
    Yes, this happens because when you search for longer text, it is more likely that this text is divided up into multiple sections, for example by P tags or other HTML tags. And therefore the text is no longer part of one single text node, its split up and managed by multiple text nodes. My simple example only looks for the search term in one single text node.

    In your case you have to take into account that the search term can cover multiple text nodes at once. This is more complicated to program, but it is doable.

  33. loosy says

    sorry, updating my last question:

    it doesn’t highlight when search string contains Link+text but otherwise it highlights link only or text only.

    Any idea?

  34. Alexander Alexander says

    @loosy
    As I said, if the highlighting spans multiple text nodes because the text is split into pieces (the highlighting crosses the borders of HTML elements like links or paragraphs) then you have to add additional code. You have to take into account that the first part of the search term might be at the end of one text node and the second part of the search term is located at the beginning of the next text node and maybe other parts are located in the following text nodes.

    Please note that the text of the web page is not found in one big “blob”. The text is split up into many smaller junks in text nodes. Therefore your code have to be able to find text that can be found across the text node boundaries.

  35. loosy says

    @Alexander
    thanks for replying. OK, I’ll make additions for this.

  36. loosy says

    @Alexander
    one more thing how can I pass the x,y coordinates and width,height array to objective-c from javascript.

    As you mentioned above of how to take coordinates of highlighted string:

    var x = 0;
    var y = 0;
    var width = node.offsetWidth;
    var height = node.offsetHeight;
    do {
    x += node.offsetLeft;
    y += node.offsetTop;
    } while (node = node.offsetParent);

    var rectArray = new Array( x, y, width, height);

    how I pass array from Javascript to objecitve-c?

  37. Alexander Alexander says

    @loosy
    There are only two ways to pass data from JavaScript to Objective C, depending of how the JavaScript code is executed, you can use one or the other.

    1) When using “stringByEvaluatingJavaScriptFromString:” to execute JavaScript code, you can directly pass back the result as string. The return value of “stringByEvaluatingJavaScriptFromString:” is of the type string on the Objective C side, but on the JavaScript side, this can be also other “simple” types like integers, which are automatically converted to strings.

    Example:
    NSString *result = [webView stringByEvaluatingJavaScriptFromString:@"var result = 0; for (i=0; i<10; i++) { result += i }; result; "];

    This would sum up all numbers from 0 to 9 and returns the result as a string. The result of the last expression in the javascript code will be returned as result.
    In the above example you can omit the “result;” at the end of the JavaScript code, because the last calculation would already contain the right value. But when in doubt, just add the variable you want to read as an expression as the last statement.

    2) If the JavaScript code is running in the background so you can’t just use “stringByEvaluatingJavaScriptFromString:” to do the whole calulation and getting the result, you can use “location.href” to pass data to the Objective C code. If you assign a URL to location.href, this URL will be opened. But first the UIWebView delegate method “urlShouldBeLoaded: webView: request: navigationType:” will be called to check if this URL should be really loaded. So you can encode your data you want to pass to Objective C as a custom URL and within the “urlShouldBeLoaded” method you check for this custom URL and if you find one, you can retrieve your data from this URL again. I’d suggest to use a certain custom URL scheme, like “coord” for coordinates, which can be easily detected in the urlShouldBeLoaded” method by inspecting the “scheme” of the URL.

    So in JavaScript you could do this

    location.href="coord:x=42&y=42&width=123&height=456"
    

    and in ObjectiveC…

      
    - (BOOL)urlShouldBeLoaded:(NSURL*)url webView:(UIWebView*)webView ...
    {
       if ([[url scheme] isEqualTo:@"coord"]) {
          // extracting the coordinates from the URL....
          return NO;
       } else {
          return YES;
       }
    }
    

    Important: Assigning a URL to location.href will open the URL in the background “later”. So the JavaScript code immediately continues execution, so you can’t directly wait for the Objective C code. In fact if you assign a second URL directly after the first one to location.href, the first assignment will most likely never be processed, because it is overwritten by the second assignment before the first got the chance to be executed. If you need to wait for the Objective C code before you continue with your JavaScript code, you have to call some JavaScript code within the Objective C code that processes the custom URL. This way you can tell the JavaScript code that it can continue.

  38. loosy says

    @Alexander
    Thanks for replying again.

    Actually I’m new to the JavaScript so I don’t know where to place this code:

    //////////////////////////////////////////////////////////////////
    // assuming ( node = element )

    var x = 0;
    var y = 0;
    var width = node.offsetWidth;
    var height = node.offsetHeight;
    do {
    x += node.offsetLeft;
    y += node.offsetTop;
    } while (node = node.offsetParent);

    //////////////////////////////////////////////////////////////////

    I placed this code right after the line of :

    MyApp_SearchResultCount++; // update the counter

    in the function of ‘MyApp_HighlightAllOccurencesOfStringForElement’

    but I’m getting ‘undefined’ values.

    If I get the coordinated that will be helpful. But finally, I want to scroll to the highlighted text as @xiang.gao wanted to do.

    Can you please help me or send me project of @xiang.gao that you might have checked.
    That will be more helpful.

  39. Alexander Alexander says

    @loosy
    You can’t use “element” here because element is a text node which doesn’t have a width or height. You should initialize “node” with the “span” element, which should have all the “offset” properties.

    And to scroll to the right location, you can use

    window.scrollTo(x,y)

  40. loosy says

    @Alexander

    Thank you so much for so much collaboration, it worked charmingly well.

  41. loosy says

    @Alexander

    one problem with scrolling is jumpy. After loading UIWebView page it suddenly jumps to the highlighted string.

    Can we scroll with animation using JavaScrip?

    I googled it and found the article:
    http://javascriptmagic.blogspot.com/2006/08/animated-page-scrolling-with.html

    that shows the example.

    when you view to the page source of the example page, it has implemented a JavaScript function that uses timer and ‘window.scrollTo(x,y)’ function to do this.

    how can I use it to achieve a smooth and animated scrolling with JavaScript.
    Any help will be appreciated.

  42. Alexander Alexander says

    @loosy
    Basically you have to scroll a short distance, define a timer to wait a short time and when the timer fires scroll a short distance again, set a timer again etc. Do this until the destination scroll location is reached.

  43. Truedon says

    Grrr…..I’ve tried and tried, and have read over and over, but I can’t find what i’m doing wrong. It refuses to highlight my text. I keeps coming back w/ result=0. Any clue of what I could be doing wrong? Could it be my webpage?? I KNOW the text/word i’m searching for is there.

  44. Alexander Alexander says

    @Truedon
    A common error is that the JavaScript file is not added correctly to the XCode project. It must be added as “Resource” file. But by default XCode would add it as normal source file to the project. But then XCode would try to compile and link the JavaScript file instead of copying it into the resources. So make sure that the JavaScript file is treated as Resource file:

    In the sidebar of the project window of your app in XCode you’ll find an item named “Target”. Open this item and you’ll find an item whose name is the name of your app. Open this as well and you’ll find several items for the different tasks which are done when building your app. The JavaScript file must be listed within “Copy Bundle Resources”. If it is not listed there, then just drag the JavaScript file from the projects window into the “Copy Bundle Resources” item.

    In case the JavaScript file was added to the “Compile Source” item of the “Target”, remove it from there.

  45. tim says

    Alexander, this is beautiful code! If you are ever in the Bay Area, look us up and we’ll buy you lunch. A question for you. The highlight works well, but in (Mobile) Safari, it seems to add 1-2 pixels enough so that when the highlight occurs multiple times in a paragraph, it can actually change where words are in the paragraph. I tried to make sure that the span margin and padding was 0px, but it didn’t seem to help. Any thoughts?

  46. Alexander Alexander says

    @tim
    This issue is probably caused by the letter-spacing, which can be negative between certain letters. For example in “To” the “o” will be placed so that the upper line of the “T” will be partly above the “o”. This way the letter placement looks much better. But when you set a background color for a SPAN element, the letters at the beginning and end of the SPAN element won’t have any negative letter-spacing anymore so that the text is placed completely inside the box. So this can add a few additional pixels around each SPAN element.

  47. sagar says

    I copied your code for demo but javascript function is not getting called.

  48. Alexander Alexander says

    @sagar
    Make sure that the JavaScript files are copied as “Resources” into the App bundle. By default XCode will treat the JavaScript files as “source code” and will try to compile and link them instead of treating them a resource files.

    In XCode 3.x you should look in the side bar, locate the “Targets” item and open it. Open the “target” itself as well and you’ll see usually three items “Copy Bundle Resources”, “Compile Sources” and “Link Binary with Libraries”. The JavaScript files must be in the first item, but are probably found in the second. So just move them from the second to the first of these items.

  49. Granular says

    This is one of greatest articles on iphone.

    I want to search through some html files (around 50) and display the results in a tableview. These html files have been added as resources in the project.

    Please help me how to search amongst all the given files and return the result along with the part of the line where the text occurs.

  50. JC says

    Thanks a lot for this article… It works great. I just started trying to learn javascript and I don’t know how to implement a scrollto next/previous highlighted result.

    A Part II of this article (something dealing with the scrollto next/previous) would be very much appreciated.

1 2 3 4 5 6



Some HTML is OK

or, reply to this post via trackback.