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 , , , , .


297 Responses

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

  1. Alexander Alexander says

    @JC
    Scrolling to the highlighted elements is very easy. If “element” is the SPAN element you want to jump to, you can simply call “element.scrollIntoView(true);” to scroll the web page so that this element will be visible at the top of the visible area. If the argument is “false” then the element will be positioned at the bottom of the visible area.

  2. JC says

    Thanks.. I put the line, span.scrollIntoView(); just before MyApp_SearchResultCount++; in the MyApp_HighlightAllOccurencesOfStringForElement function, and it automatically scrolls to the first highlighted searched word.

    The problem I have is how to get to the next highlighted element when I push a UIButton… Do I make a function like this?

    function MyApp_GetToNextElement(element) {
    if (element) {
    if (element.nodeType == 1) {
    if (element.getAttribute(“class”) == “MyAppHighlight”) {
    var next = element.nextSibling;
    next.scrollIntoView();
    ……..

    or is there an index that I could access to return a particular element?
    Thanks a lot for your help.. I’m still very new to javascript/programming.

  3. Pask says

    @JC
    You solved the problem? I am very interested to the solution!

  4. Alexander Alexander says

    @JC
    You simply have to find the next element with the “class” attribute value of “MyAppHighlight” and call scrollIntoView() on it. Finding the next element is not that simple than just using “nextSibling”, because you’re navigating in a complex tree structure. The next element you’re looking for could be in another branch, so it’s not necessarily a “sibling” of the current element. There are multiple ways to solve this task.

    You can use the “treeWalker” object of JavaScript, which lets you walk through the HTML tree from the beginning to the end like it was a linear list of elements. Though for a beginner it might look complicated at first.

    Another approach would be to save all the occurrences of the search (the SPAN elements) in a array while you create them, This way you don’t need to search for these elements anymore later, you only need to remember the index of the current highlighted element. When you want to go to the next element, increment the index, if you want to go to the previous element, decrement the index, then access the array with this index to get the SPAN element and call scrollIntoView() on it. This is probably the easiest way to solve this task.

    To create an array, use
    var a = new Array();

    add an element to the array…
    a.push(element);

    find out how many elements the array contains:
    var count = a.length;

    Access element at index i…
    var element = a[i];

  5. JC says

    Thanks a lot sir… Your solution works great!!! Your help is very much appreciated.

    This is what I basically did..

    I created an array and made it a global variable in SearchWebView.js:
    var a = new Array();

    then I added span elements to that array while it was searching(inside the function MyApp_HighlightAllOccurencesOfStringForElement):
    span.style.color=”black”;
    a.push(span); // <—- I added this
    text = document.createTextNode(value.substr(idx+keyword.length));

    Then I added this when I search to get a starting position:
    NSString *result = [detailsLabel stringByEvaluatingJavaScriptFromString:@"a.length"];
    currentPosition = [result intValue]-1;

    Then to scroll to next/previous I did this:
    - (void) nextHighlight:(id)sender {
    … // <— some code to check if currentPosition is valid
    currentPosition = currentPosition -1;
    NSString *scrollPosition= [NSString stringWithFormat:@"a[%d].scrollIntoView()", currentPosition];
    [myWebView stringByEvaluatingJavaScriptFromString:scrollPosition];
    }

    - (void) previousHighlight:(id)sender {
    … // <— some code to check if currentPosition is valid
    currentPosition = currentPosition +1;
    NSString *scrollPosition = [NSString stringWithFormat:@"a[%d].scrollIntoView()", currentPosition];
    [MyWebView stringByEvaluatingJavaScriptFromString:scrollPosition];
    }

    Again.. Alexander, Thanks a lot.

  6. sandy says

    Can any 1 tell me how can i add the given java script file with my iPhone app?

  7. nash says

    Hi Alexander,

    Did you receive my email? Everything works fine, and the NSLog says that the word is highlighted, I even took the advise of moving the js document to the copy bundle resources, but the word never turns yellow or looks highlighted. What is wrong with my webview that is does not paint the words in yellow? Please help.

  8. Alexander Alexander says

    @nash
    In the sample project you’ve sent me the JS file was not yet moved to the ” copy bundle resources” section. And this was the only reason why it didn’t work. After I’ve moved the file into the “copy bundle resources” section, everything worked just fine.

  9. SP says

    Dear Alexander,

    Thanks a lot, you have made it much easier for newbies like me.

    I have a question though that I am grappling with. I want to keep my apps UI native, in Obj C using regular nib and UIView. However, I have a javascript api which I use to call the server side to send messages to the server. The js API internally uses XmlHttpRequest. However, I am unable to get it working. I am able to load the lib and app js resources, call the function, I see the alerts in the js functions. But since my JS code as server side callbacks, I never get to see the data coming back. Below is a code snippet. I have kept is basic for now, i.e. on button click, I am initializing the UIWebView, and making the call.

    It works if I use a framework like phonegap, where the GUI itself is based on UIWebView.

    I’ll appreciate any thoughts/help/pointers you can throw my way…

    - (IBAction)doSend:(id)sender {

    //js experiment

    UIWebView *aWebView;

    // init and create the UIWebView
    aWebView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
    self.solWebView = aWebView;

    //load solclient JS
    NSString *pathSolLib = [[NSBundle mainBundle] pathForResource:@”myjslib” ofType:@”js” inDirectory:@”js”];
    NSString *jsCodeSolLib = [NSString stringWithContentsOfFile:pathSolLib encoding:NSUTF8StringEncoding error:nil];
    [self.solWebView stringByEvaluatingJavaScriptFromString:jsCodeSolLib];

    //load app JS
    NSString *path = [[NSBundle mainBundle] pathForResource:@”myjsappfuncs” ofType:@”js” inDirectory:@”js”];
    NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    [self.solWebView stringByEvaluatingJavaScriptFromString:jsCode];

    NSString *sendCall = [NSString stringWithFormat:@"myAppFunction();"];
    NSLog(@”Test 1: %@”, sendCall);
    NSString *result = [self.solWebView stringByEvaluatingJavaScriptFromString:sendCall];

    chatText.text = result;
    NSLog(@”Test 2, result: %@”, result); // i get nothing in the result
    [aWebView release], aWebView = nil;
    }

  10. Alexander Alexander says

    @SP
    It is difficult to tell what exactly happens here, because I don’t know what the JS code is doing. Especially XmlHttpRequests do usually need some time to complete, so it is likely that this call has not yet received the answer from the server when you actually look for that answer.

    Please note: UIWebView loads and renders its content in the background, so you usually need to wait a short time to give it the opportunity to do it’s work. For asynchronous tasks, let the JavaScript code notify the Cocoa code when it is ready. You can do this by assigning a special URL to “location.href” which can also contain the result in its URL parameters. The UIWebView delegate method “shouldStartLoadWithRequest:” will be called with this URL.
    But be careful: assigning a value to location.href won’t be processed immediately, so you can’t assign values to location.href too short after the first assignment, because the first can be overwritten if it is not yet processed.

    And as usual make sure that the JS code is really added as “resource” file to your App, because by default XCode will treat them as source code and won’t copy it into the APp bundle as resources.

  11. Smriti says

    I’m using same code but it’s not working. I have added my js file in resources still i get path as nil in SearchWebView.m. I’m using xcode 4.0.2.

  12. Alexander Alexander says

    @Smriti
    Most likely you have not added the JS file into the “copy bundle resources” section of the “Build Phases” of your project targets.

    Under XCode 4 you need to first open the “TARGETS” section of your Project and then you’ll find the options Summary, Info, Build Settings, Build Phases and Build Rules. Here select “Build Phases” which should reveal several lists. Make sure that the JS file is in the list “Copy Bundle Resources”. If it is in the list “Compile Source”, please mode it to “Copy Bundle Resources”.

  13. Mario says

    Thanks for this!
    But I have a question about that. Imagine you want to render this webview into an UIImage, which has to be done in – (void)webViewDidFinishLoad:(UIWebView*)webView; I was trying to inject this (and whatever) javascript before making my calls to [[webView layer] renderInContext:context]; for example.
    The problem is that the result of the injection is never shown on the image captured. Did someone stumble upon that maybe?

  14. Alexander Alexander says

    @Mario
    As you’ve noticed, you can not do this in “webViewDidFinishLoad:”. Ad the name of this method indicates, loading the page is finished (well, there’s a bug in the iOS, so this is not always the case, see below), but this does not mean that the web page is already fully rendered. Rendering is done in the background, and it can take a while after the last byte of the page was loaded until the rendering process is also finished.
    Unfortunately UIWebView does not tell us, when the rendering is finished, so all you can do is to simply wait for half a second or so and then create your UIImage.

    The bug in the iOS, which will result that “webViewDidFinishLoad:” is called before the web page has really finished loading shows itself on web pages with “frames” or “iframes”. Though the UIWebView API hides the fact that there are things like frames (unlike the WebView API of the Mac, which has many methods and classes to deal with frames), it nevertheless calls “webViewDidFinishLoad” and “webViewWillStart” methods for each frame. So you may need to count how often these methods are called to find out when the page is really finished.

  15. Mario says

    @Alexander:
    Thank you for this helpful information. I am still a bit confused.
    My problem is that I can render the Webpages (not containing any frames) perfectly in webviewDidFinishLoad. So before rendering I`d like to inject some script to alter the (local) webpage displayed. As far as I am informed at the moment, the injection can only be done in webviewDidFinishLoad or am I totally wrong here?

  16. Mario says

    @Alexander: i forgot to mention the following:
    I am injecting the js in webviewDidFinishLoad, wait for some time ( 1 second) and then start to create the UIImage with [[webView layer] renderInContext:context];
    In this case (using your js from above) the UIImage contains the whole webside but no search text is highlighted. What really confuses me is the fact that every call like NSString *str = [webView stringByEvaluatingJavaScriptFromString:@"document.title"]; for example works fine, so “str” contains the value fetched.

  17. Alexander Alexander says

    @Mario
    As I said, the web page rendering is not necessarily finished when the “webviewDidFinishLoad:” method is called. This means injecting the JS now can result that the code can not address the whole HTML tree, because the HTML tree is not yet completed.
    So you should wait 1 second first, then inject your Javascript code and then create your image.
    Depending of the JavaScript code that is injected, it is even possible that the web page needs to be re-rendered. And then you even need to wait for another short time before you can actually create the image. When modifying the HTML tree (for example adding the highlights for the search results) the UIWebView may need some time to layout the new HTML structure and display it. So if you create your image too early, the re-layout process is not yet finished.
    Please note that UIWebView uses background threads to do the rendering and layouting and the other stuff, like downloading the data.

  18. Mario says

    Thank you Alexander! I`ve tried all possible variations of this ( waiting another second before injecting, then waiting another before drawing / injecting, reloading…). Still no Luck. I guess I`ll have to go for another solution without injecting since waiting such a long time (2 seconds by now + writing the image to disk) will result in a lack of usability in my program. Actually I was trying to alter a HTML-File so its text could be splitted into pages and the use of Javascript seemed a pretty cheap solution for this. So I stumbled on this thread. After all I guess Its better to split the pages before they are displayed in the UIWebview.
    But thank you for your time and effort on my question! I am sure I will need this information some other time.
    Kind regards,
    Mario

  19. Alexander Alexander says

    @Mario
    Is the result of the JS injection visible on the screen? If it is not, then maybe the problem is within the JS code, which might have an error and therefore doesn’t do what you’ve expected. Normally after 1-2 seconds it should be save to assume that the page has finished the rendering and layout process.

  20. Mario says

    @Alexander: No result on the screen, not even when its a simple thing like a document.write(“whatever sentence”);

  21. Mario says

    OMG, just noticed something. I am actually working with xml-files. For some reason I am not yet aware of javascript doesnt seem to be executed properly on those. Didn`t notice this because the fetching of i.e document.title worked fine.

  22. Mario says

    @Alexander: Sorry for wasting your time with this. Actually I dont even need to wait after the injection if it is a HTML-File. I wonder if UIWebview can be set to execute JS in XML-Files.

  23. Alexander Alexander says

    @Mario
    Yes, my code from above expects HTML code, so it won’t work with XML. I’ve never tried to do this on XML file, but it might work. But you have to rewrite the JavaScript code to match the XML rules. For example, XML has no “document.body” property. Also “span” elements are probably not what you want to insert.

    For simple HTML files, you probably don’t need to wait “webviewDidFinishLoad:”, but for complex web pages you’ll probably get some problems if you don’t wait. So if you know exactly how the HTML code will look like that is loaded, then you can try it without waiting. But if you need to deal with any webpage from the web, then you better wait 0.5 seconds or so until you inject the JS code. BTW: this can also depend on the device. Older and slower devices might need a longer time until the rendering is finished, the new iPad 2 might be fast enough so that even some more complex web pages will work without waiting.

  24. Mario says

    @Alexander: Actually the xml-files have HTML-Syntax (its epub-chapters).
    for example it could look like this:

    CHAPTER 1 – Jonathan Harker’s Journal | Dracula

    CHAPTER 1
    Jonathan Harker’s Journal
    3 May. Bistritz.—Left Munich at 8:35 P.M., on 1st May, arriving at Vienna early next mo

    I`ve tried to rename them and yes, the paginating with Javascript takes too much time. All added up its about 2 seconds wasted for the purpose (on older devices like iPhone3G). I guess I`ll go back to my former approach and paginate the chapter-xml-files before even rendering them. That might waste some loading time at the beginning but is probably the better tradeoff.
    Neverless the information on this blog was very helpful. Never knew you could even communicate with a UIWebview like that. I am sure this will become handy for me in the future.

  25. Mario says

    crap! should have somehow quoted that html ;-)

  26. Sarang says

    Thank u sir for putting this code. but, my code is not working i read all comments and follow changes what i required still its not working. If u hv sample code help for that. Thank You.

  27. Alexander Alexander says

    @Sarang
    You don’t tell what exactly doesn’t work, which makes it difficult to help and solve the problem.

    The most common issue is that the JS files are not added as “Resource files” to your XCode project, so they do not end up in as Resource file in the App bundle. And then your app can not load the JS file and nothing works.

    So make sure the JS files are moved to the right location in XCode so that they are treated as Resource files. See my comment to Smriti above.

  28. Carl says

    Very interesting post!thank you!!i’ve implemented the code you writed, i’ve created 3 classes with the code. Then i have my webview with the HTML text…

    - (void)loadWebView1 {
    UIWebView *webView1 = [[UIWebView alloc] init];

    NSString *indirizzo = [[NSBundle mainBundle] pathForResource:@”codicedeo” ofType:@”html”];
    NSURL *webURL = [NSURL fileURLWithPath:indirizzo];

    NSURLRequest *webURLRequest = [NSURLRequest requestWithURL:webURL];
    [webView1 loadRequest:webURLRequest];
    [webView1 setScalesPageToFit:NO];
    [webView1 setFrame:CGRectMake(0, 52, 320, 316)];
    [self addSubview:webView1];
    [webView1 highlightAllOccurencesOfString:@"keyboard"];
    }

    this last istruction xcode says: “instance method highlightAllOccurencesOfString not found ” so how can i associate thii webview with the SearchWebView.m?!

    maybe it’s a stupid question but i’m a newbie!!!! Thank you in advance

  29. Alexander Alexander says

    @Carl
    Your code won’t work this way. There are several errors

    - You need to include the category code (SearchWebView.m, see above) where the “highlightAllOccurencesOfString” method for the UIWebView is defined into your project. If you don’t do this, highlightAllOccurencesOfString is not defined and can not be called.

    - The “loadRequest” call is not a synchronous call, so when this method returns the request is NOT yet finished and also the web page is not loaded and rendered at that time, so “highlightAllOccurencesOfString” would never have anything to work on. So you need to wait until the web page is loaded and rendered before you can search for anything. This means you need to implement the UIWebView delegate method “webViewDidFinishLoad:” to get notified when the web page has finished loading.

  30. Carl says

    Hi alexander thank you for your answer!i’ve read also yuor others comment and i’ve understand the second point.
    As for the first point i’m still in trouble!i’ve put the SearchWebView.m in my project…how can i call it from the uiwebview written before?!?thank you i advance again!!!

  31. Alexander Alexander says

    @Carl
    You can just call it. Just don’t forget to “#import” the header file, so the compiler is happy and knows where the “highlightAllOccurencesOfString” method is defined.

  32. Sarang says

    Hi thank u but i did that one. But problem is the JS file not execute. by below code :
    NSString *startSearch =
    [NSString stringWithFormat:@"MyApp_HighlightAllOccurencesOfString('%@')",str];

    NSLog(@”# StartSearch :%@”,startSearch);
    [self.pageView stringByEvaluatingJavaScriptFromString:startSearch];

  33. Carl says

    i’ve send you an email!!!!

  34. Alexander Alexander says

    @Sarang
    Most likely you did not add the JS file as “resource” file to your project.

    Make sure you move the JS file in the section “Targets/build phases” of your XCode project from the “Compile Source” list to the “Copy Bundle Resources” list.

  35. Vlad says

    Is it possible to use a UISearchBar when searching a UIWebView?

  36. Alexander Alexander says

    @Vlad
    Sure, you can use what ever you want to enter the search term. But in any case you need to manually get the search term from the text field or search bar and then call the search method/function. The search bar is just a text field optimized for searches, but it does not do the search in UIWebView. It just helps the user to enter the search term.

  37. Cullen SUN says

    Hi Alexander,
    This is a great tutorial. It is so helpful for developers who wanna interact with UIWebView. This tutorial is a kind of that I cannot just walk away without saying “Thank you”. Appreciated your effort for putting this up.
    I was working on a project that needs to inject some javascript to a webview. Happened to click a link of your blog , so lucky. I would add this search function to my webview.

    Cheers.

  38. sud says

    Hi Alexander,
    I want to scroll the UIWebView(containing RTF) for particular position depending on the text and font size , can u give tell how it can be done?

  39. sud says

    Hi Alexander,
    im able to scroll but i want to search depending on font size how it can be done……pls let me know…..

  40. Alexander Alexander says

    @sud
    I fear that this is not really possible. In HTML you can use JavaScript to find the section you want to scroll to and measure its coordinates. But for RTF you can’t do this, AFAIK.

  41. sud says

    Hi alexander,
    the code u gave is working fine, but i need to add one more condition to scroll based on the height of the SPAN or the text, can u provide some suggestion for this?

  42. Alexander Alexander says

    The width and height of (block-level) HTML elements can be determined very easy by using the “offsetWidth” and “offsetHeight” property…

    var height = element.offsetHeight;

    For inline elements it’s not always clear how to measure the width or height, because these can span multiple lines and their outline is often not a simple rectangle with a certain width or height.

  43. sud says

    Hi Alexander,
    is it possible search based on the font size for particular text? can i know how can i get the font size of text in java script?

  44. Alexander Alexander says

    @sud
    You can check the current value for the CSS property “font-size” that is assigned for a certain element. But in general it would be a bad idea to set the font size as search parameter, because the user can change the for size as well (at least in a browser context).

    But if the document has some well-defined structure (certain tags, headings like H1..H6, class attributes ect.) you might take certain tags and attribute values into account when searching.

  45. sud says

    @Alexander :- actually in my case its a RTF , i dont have tag to identify that particular text, but that text “font size ” will be unique in the RTF. can u suggest me some idea, i wants to scroll to that particular text (now im able to scroll , comparing on the just text ) , but same text can repeat in RTF , only unique is FONT SIZE….pls suggest me some idea

  46. sud says

    @Alexander :-
    i just assigned font size and tried still it didnt work, anything wrong in my JS?when font size equals 14 i want to scroll.
    var x = element.fontSize;;
    if(x==”14″)
    span.scrollIntoView();

  47. Alexander Alexander says

    @sud
    For HTML you should check the “computed Style” for an element. CSS can inherit properties, so even if an element does not explicitly have a font-size rule, it inherits one from its parent element. So your approach would not work. There fore you should try this…

    var fontSize = window.getComputedStyle(element, false).fontSize;

    Please note, this will also return the unit for the font size, as you would define it in CSS, so a result could be for example “12px”.

    But in any case, I’m not sure if this works for RTF as well, because RTF is not HTML (so no SPAN elements) and also RTF doesn’t use CSS for styling. Only if UIWebView would translate a RTF document into a HTML document, this can work.

  48. sud says

    @Alexander:-
    i think UIwebView internally converts RTF to HTML , because im able to change the font size using – span.style.fontSize=”14″; But im nt able to get the font size of searched font, even i used
    var fontSize = window.getComputedStyle(element, false).fontSize; alert(fontSize); its not giving the value at all……. Any idea?

  49. sud says

    @Alexander:- im able to get the font size by- window.getComputedStyle(span, false).fontSize , i need one more , can i check wether text as underline ?

  50. Alexander Alexander says

    @sud
    Instead of the fontSize property you should try the “textDecoration” property. This can have a list of values, like “underline”, “line-through”, “overline” etc.

1 2 3 4 5 6



Some HTML is OK

or, reply to this post via trackback.