iOS_webView

来源:互联网 发布:智慧医疗 物联网 知乎 编辑:程序博客网 时间:2024/06/06 14:17

1、改变字体大小

2、在webView.scrollView中添加headerView和footerView

3、高亮搜索结果

4、弹出自定义选择菜单


scrollView滚动到底部

- (void)scrollsToBottomAnimated:(BOOL)animated{    CGFloat offset = self.tableView.contentSize.height - self.tableView.bounds.size.height;    if (offset > 0)    {        [self.tableView setContentOffset:CGPointMake(0, offset) animated:animated];    }}




1、改变字体大小

先:


[NSString stringWithFormat:@"<html> \n"
                                 "<head> \n"
                                 "<style type=\"text/css\"> \n"
                                 "body {font-family: \"%@\"; font-size: %f; color: %@;}\n"
                                 "</style> \n"
                                 "</head> \n"
                                 "<body>%@</body> \n"
                                 "</html>", @"宋体", fontSize,fontColor,stringValue] ;

再:

[_webViewstringByEvaluatingJavaScriptFromString:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '150%'"];




下面的这个不行。。。


NSString *jsString = [[NSString alloc] initWithFormat:@"document.body.style.fontSize=%f;document.body.style.color=%@",fontSize,fontColor];
[webView stringByEvaluatingJavaScriptFromString:jsString]; 



2、缩放

2.设置webView内容宽度等于屏幕宽度显示,设置webView的缩放效果

NSString *meta = [NSString stringWithFormat:@"document.getElementsByName(\"viewport\")[0].content = \"width=self.view.frame.size.width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\""];    [webView stringByEvaluatingJavaScriptFromString:meta];//(initial-scale是初始缩放比,minimum-scale=1.0最小缩放比,maximum-scale=5.0最大缩放比,user-scalable=yes是否支持缩放)





关于UIWebView的一些事(进度条私有实现)   管理提醒: 本帖被 linlinyao1 从 开发资源区 移动到本区(2012-11-06)市面上的浏览器都做了进度条,但是资料找来找去都是私有API,有什么解决方案吗?能上架的私有调用方法有没有?请大神PM一个进度条用了私有方法实现,上架成功,不知道是不是运气。这里就不公开了,卖个关子  最近做了浏览器的项目,和UIWebView大了不少交道,所以专门开个帖子讲讲相关内容吧,想到什么就更新什么             基础篇:NSURL介绍 http://blog.csdn.net/ysy441088327/article/details/7416759网页执行js代码复制代码stringByEvaluatingJavaScriptFromString这个方法是让一切成为可能的关键,有了这个方法,才能对网页进行各种操作。我自己没有做过网页开发,所以对js不熟悉,只用了一些最常用的js,如果非常熟悉的话应该能做更多的事。从网页获取URL:复制代码- (NSURL*)url{    NSString *urlString = [self stringByEvaluatingJavaScriptFromString:@"location.href"];    if (urlString) {        return [NSURL URLWithString:urlString];    } else {        return nil;    }}从网页获取标题:复制代码- (NSString*)title{    return [self stringByEvaluatingJavaScriptFromString:@"document.title"];}网页的滚动位置:复制代码- (CGPoint)scrollOffset {    CGPoint pt;    pt.x = [[self stringByEvaluatingJavaScriptFromString:@"window.pageXOffset"] integerValue];    pt.y = [[self stringByEvaluatingJavaScriptFromString:@"window.pageYOffset"] integerValue];    return pt;}网站的图标Icon下载地址:复制代码NSURL *url = [[NSURL alloc] initWithScheme:[web.request.URL scheme] host:[web.request.URL host] path:@"/favicon.ico"];调整webView里的字体大小http://www.cocoachina.com/bbs/read.php?tid=29707判断网页URL是否合法(自己写的,未经过大量验证):复制代码+(BOOL)isValidWebUrl:(NSURL *)url{    BOOL valid = NO;    if (url) {        if (!url.scheme.length) {            url = [NSURL URLWithString:[@"http://" stringByAppendingString:url.absoluteString]];        }        if (url.host.length) {NSString * regex        =  @"^([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?[    wind_phpcode_5    ]quot;;             NSPredicate * pred      = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];            if ([pred evaluateWithObject:url.host]) {                valid = YES;            }        }    }    return valid;}判断url是否相同:NSURL的isEqual方法不太好用。最常见的问题是http://www.google.com和http://www.google.com/会判断不同,原因是苹果在这里没有遵循RFC2616规则做网址对比,所以最好自己判断下最后的斜杠加载本地html的css和图片:基本上就是加载本地文件的url就可以了,但如果是有外部的css和图片等资源,记得拖资源的时候记得要选择下面的create folder references for any added folder,不要选group,这样才能获得正确的路径关系。提高篇:这里直接引用一个国外博客,先放着,日后再翻译自定义网页上的长按弹出菜单:http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/网页内关键字搜索与高亮:http://www.icab.de/blog/2010/01/12/search-and-highlight-text-in-uiwebview/自动根据网页打开新标签和新窗口:http://www.icab.de/blog/2009/07/27/webkit-on-the-iphone-part-1/http://www.icab.de/blog/2009/08/05/webkit-on-the-iphone-part-2/只是上面2个还不够,有些网页的_blank不是写在链接上的,而是全局都是_blank,所以再补上下面的部分才行复制代码function MyIPhoneApp_isBlankInBaseElement() {    var baseElements = document.getElementsByTagName('base');    if(baseElements.length > 0){        if(baseElements[0].getAttribute('target') == '_blank'){            return 'yes';        }    }    return 'no';}清除UIWebVIew的内存占用和泄露:http://www.codercowboy.com/code-uiwebview-memory-leak-prevention/地址栏随着网页下拉移动,类似safari:先拿到webView的scrollView,如果5.0以下就用靠循环去找了复制代码-(UIScrollView *)getWebScrollView{    UIScrollView* scroll = nil;    if ([self respondsToSelector:@selector(scrollView)]) {        scroll = [self scrollView];    }else{        for (UIView* view in [self subviews]) {            if ([view isKindOfClass:[UIScrollView class]]) {                scroll = (UIScrollView*)view;                break;            }        }    }    return scroll;}然后把设置delegate,实现scrollViewDidScroll方法复制代码-(void)scrollViewDidScroll:(UIScrollView *)scrollView{    CGPoint contentOffset = scrollView.contentOffset;    CGFloat threshold = self.topBar.frame.size.height; // topBar就是webview上面的地址栏    if(contentOffset.y>=-threshold &&contentOffset.y<=480 )  // 480设置得有些大了,这里是为了防止快速滑动的时候回调会跟不上,    {        self.topBar.frame = CGRectMake(0,-threshold-contentOffset.y, 320, self.topBar.frame.size.height);    }    else if(contentOffset.y<-threshold){        self.topBar.frame = CGRectMake(0, 0, 320, self.topBar.frame.size.height);    }}停止和播放网页上的视频:iPad上在网页里播放视频,即使关掉webView,依旧余音绕耳,调用下面的js可以停止播放复制代码function stopVideo(){    var videos = document.querySelectorAll("video");    for (var i = videos.length - 1; i >= 0; i--){        videos.pause();    };    return 'stop';}离线缓存网页:http://re-reference.iteye.com/blog/1391408网页加载进度不靠谱模拟:一般情况下,加载一个网页会经历should->start->finish3个阶段。仔细看UIWebViewDelegate的文档,会发现这里的delegate针对的是每个frame,也就是说如果网页由多个frame组成的话会有多个start。那么这里的finish也会被调用多次,所以仅靠finish是无法判断网页是否加载完全的。事实是,加载任意网页,srart和finish/fail是配对的,也就是说有多少个start就有多少个finish/fail。所以在start做count++,finish/fail做count- -,为0的时候就是一个网页加载完毕了。 不过别高兴太早,加载完毕不代表网页渲染完毕,所以在最后还需要做一个延时操作,以确保网页确实显示出来了。好了,现在网页开始和网页结束都找到了,但中间过程仍然是个谜,只能不靠谱模拟了。












Customize the contextual menu of UIWebView

When you tap and hold your finger on a link in a UIWebView object for about a second, a contextual menu will open and provides a few choices: copy the URL to the pasteboard, open the link and a button to close the menu again. Unfortunately there’s no API available to add additional menu items to this contextual menu. But if you look at iCab Mobile, you’ll notice that there are a lot of additional menu items here. How is this done?

First of all, you really can’t add additional menu items to the default ones of the standard contextual menu. But you can switch off the contextual menu using a certain CSS property. So the solution would be to switch off the default menu and implement your own from scratch. And to implement your own contextual menu, you have to first catch the tab-and-hold gesture, get the coordinates of the finger on the screen, translate these coordinates into the coordinate system of the web page and finally look for the HTML elements at this location. Based on the HTML elements, you can then decide which menu items to include in the contextual menu, and then create and display the contextual menu.

The first step is to switch off the default contextual menu of UIWebView. This is easy because we only need to set the CSS property “-webkit-touch-callout” to “none” for the body element of the web page. We can do this using JavaScript in the UIWebView delegate method “webViewDidFinishLoad:”…

- (void)webViewDidFinishLoad:(UIWebView *)webView{   [webView stringByEvaluatingJavaScriptFromString:@"document.body.style.webkitTouchCallout='none';"];}

Now, the default Contextual menu won’t open anymore.

The next step is to catch the “tap-and-hold” gesture. If your App only needs to run on iOS 3.2, iOS 4.0 or newer, you can simply use the new UIGestureRecognizer API. But if you still want to address the 1st generation iPod Touch and iPhone devices (where you can install at most iOS 3.1) where these UIGestureRecognizer classes are not available, you need to do a little bit more. I’ll show here a solution which will work with iOS 3.0 as well, so it’s compatible to all iPhone, iPad and iPod Touch devices.

A big problem when you need to catch gestures within UIWebView is that UIWebView internally consists of many nested views, and only the inner ones do respond to events and gestures. Which means, if you subclass UIWebView and overwrite the methods “touchesBegan:withEvent:”, “touchesMoved:withEvent:” etc., you’ll notice that these methods won’t be called. The events are delivered directly to these private inner views which are actually doing all the work. And if you overwrite “hitTest:withEvent:”, which is used by the iOS to find the view to which the events will be delivered, you would be able to get the touch events, but then you would have to deliver the events to these inner views yourself so these can still do their work. And because these inner views are private and the relationships between these views are unknown, it can be extremely dangerous to mess with the events here.

A much easier way would be to subclass UIWindow and overwrite the method “sendEvent:” of the UIWindows class. Here you’ll get all events before they are delivered to the views. And we can deliver the tap-and-hold events using the notification manager to the rest of the app. Each object that is interested in this gesture can listen to this notification.

Recognizing the tap-and-hold gesture is not very complicated. What we need to do is to save the screen coordinates of the finger when the finger first touches the screen. At that time we will also start a timer which fires after about a second. As soon as another finger touches the screen, the finger moves or the touch event is canceled, we invalidate the timer because then it can not be a simple tap-and-hold gesture anymore. If the timer fires, we can be sure that a single finger has touched the screen for about a second without moving and then we’ve recognized the “tap-and-hold” gesture. We post the gesture as notification.

This is the implementation of the UIWindow subclass:

MyWindow.h:

@interface MyWindow : UIWindow{   CGPoint    tapLocation;   NSTimer    *contextualMenuTimer;}@end

MyWindow.m:

#import "MyWindow.h"@implementation MyWindow- (void)tapAndHoldAction:(NSTimer*)timer{   contextualMenuTimer = nil;   NSDictionary *coord = [NSDictionary dictionaryWithObjectsAndKeys:             [NSNumber numberWithFloat:tapLocation.x],@"x",             [NSNumber numberWithFloat:tapLocation.y],@"y",nil];   [[NSNotificationCenter defaultCenter] postNotificationName:@"TapAndHoldNotification" object:coord];}- (void)sendEvent:(UIEvent *)event{   NSSet *touches = [event touchesForWindow:self];   [touches retain];   [super sendEvent:event];    // Call super to make sure the event is processed as usual   if ([touches count] == 1) { // We're only interested in one-finger events      UITouch *touch = [touches anyObject];      switch ([touch phase]) {         case UITouchPhaseBegan:  // A finger touched the screen            tapLocation = [touch locationInView:self];            [contextualMenuTimer invalidate];            contextualMenuTimer = [NSTimer scheduledTimerWithTimeInterval:0.8                        target:self selector:@selector(tapAndHoldAction:)                        userInfo:nil repeats:NO];            break;         case UITouchPhaseEnded:         case UITouchPhaseMoved:         case UITouchPhaseCancelled:            [contextualMenuTimer invalidate];            contextualMenuTimer = nil;            break;      }   } else {                    // Multiple fingers are touching the screen      [contextualMenuTimer invalidate];      contextualMenuTimer = nil;   }   [touches release];}@end

Some remarks for the UITouchPhaseMoved phase: it can be sometimes useful to allow small movements on the screen. You can add some code to check for the distance the finger has moved and if it is within a certain range, you just don’t abort the timer. This helps users which have difficulties to hold the finger still for about a second.

Another important thing you have to do when the App window is created by a NIB file: you have to change the UIWindow class of the window within the NIB file in Interface Builder to the new subclass MyWindow. This way the window is created with our subclass, which is important.

The next step is to listen for the “TapAndHoldNotification” notification within the UIWebView delegate and when this notification is received, we need to check which HTML element was touched.

When initializing the UIWebView delegate, we need to add the delegate as observer for the notification…

   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextualMenuAction:) name:@"TapAndHoldNotification" object:nil];}

And here’s the method “contextualMenuAction:”…

- (void)contextualMenuAction:(NSNotification*)notification{   CGPoint pt;   NSDictionary *coord = [notification object];   pt.x = [[coord objectForKey:@"x"] floatValue];   pt.y = [[coord objectForKey:@"y"] floatValue];   // convert point from window to view coordinate system   pt = [webView convertPoint:pt fromView:nil];   // convert point from view to HTML coordinate system   CGPoint offset  = [webView scrollOffset];   CGSize viewSize = [webView frame].size;   CGSize windowSize = [webView windowSize];   CGFloat f = windowSize.width / viewSize.width;   pt.x = pt.x * f + offset.x;   pt.y = pt.y * f + offset.y;   [self openContextualMenuAt:pt];}

The method “scrollOffset” and “windowSize” are implemented as category for the UIWebView class. “scrollOffset” is required to make sure the coordinates are also correct when the web page was scrolled. The “windowSize” returns the visible width and height of the HTML document from the point of view of the HTML document. So based on the windowsSize of the “HTML window” and the view size of the UIWebView, you can calculate the zoom factor, and the zoom factor is necessary to transform and scale the screen coordinates to the correct HTML coordinates.

Here’s the implementation of “scrollOffset” and “windowSize”…

WebViewAdditions.h:

@interface UIWebView(WebViewAdditions)- (CGSize)windowSize;- (CGPoint)scrollOffset;@end

WebViewAdditions.m:

#import "WebViewAdditions.h"@implementation UIWebView(WebViewAdditions)- (CGSize)windowSize{   CGSize size;   size.width = [[self stringByEvaluatingJavaScriptFromString:@"window.innerWidth"] integerValue];   size.height = [[self stringByEvaluatingJavaScriptFromString:@"window.innerHeight"] integerValue];   return size;}- (CGPoint)scrollOffset{   CGPoint pt;   pt.x = [[self stringByEvaluatingJavaScriptFromString:@"window.pageXOffset"] integerValue];   pt.y = [[self stringByEvaluatingJavaScriptFromString:@"window.pageYOffset"] integerValue];   return pt;}@end

Finally, we need to implement the method “openContextualMenuAt:” for the UIWebView delegate, which first checks for the HTML elements that are at the touch locations and the creates the contextual menu. Checking for the HTML elements at the touch location must be done via JavaScript…

JSTools.js

function MyAppGetHTMLElementsAtPoint(x,y) {   var tags = ",";   var e = document.elementFromPoint(x,y);   while (e) {      if (e.tagName) {         tags += e.tagName + ',';      }      e = e.parentNode;   }   return tags;}

This JavaScript function simply collects the tag names of all HTML elements at the touch coordinates and returns the tag names as string list. The JavaScript file must be added as “resource” to your XCode project. It can happen that Xcode treats JavaScript file as normal code and tries to compile and link it instead of adding it to the resources. So make sure the JavaScript file is in the “Copy Bundle Resources” section within the XCode project target and not in the “Compile Sources” or “Link Binaries” section.

- (void)openContextualMenuAt:(CGPoint)pt{   // Load the JavaScript code from the Resources and inject it into the web page   NSString *path = [[NSBundle mainBundle] pathForResource:@"JSTools" ofType:@"js"];   NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];   [webView stringByEvaluatingJavaScriptFromString: jsCode];   // get the Tags at the touch location   NSString *tags = [webView stringByEvaluatingJavaScriptFromString:            [NSString stringWithFormat:@"MyAppGetHTMLElementsAtPoint(%i,%i);",(NSInteger)pt.x,(NSInteger)pt.y]];   // create the UIActionSheet and populate it with buttons related to the tags   UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"Contextual Menu"                  delegate:self cancelButtonTitle:@"Cancel"                  destructiveButtonTitle:nil otherButtonTitles:nil];   // If a link was touched, add link-related buttons   if ([tags rangeOfString:@",A,"].location != NSNotFound) {      [sheet addButtonWithTitle:@"Open Link"];      [sheet addButtonWithTitle:@"Open Link in Tab"];      [sheet addButtonWithTitle:@"Download Link"];   }   // If an image was touched, add image-related buttons   if ([tags rangeOfString:@",IMG,"].location != NSNotFound) {      [sheet addButtonWithTitle:@"Save Picture"];   }   // Add buttons which should be always available   [sheet addButtonWithTitle:@"Save Page as Bookmark"];   [sheet addButtonWithTitle:@"Open Page in Safari"];   [sheet showInView:webView];   [sheet release];}

This method injects the JavaScript code which looks for the HTML elements at the touch location into the web page and calls this function. The return value will be a string with a comma-separated list of tag names. This string will start and end with a comma so we can simply check for occurrences of a substring “,tagName,” if we want to find out if an element with a certain tag name was touched. In our example, we simple add some buttons if an “A” tag was hit and some other buttons if and “IMG” tag was hit. But what you’re doing is up to you. Also the information that is returned from the JavaScript function (in this example “MyAppGetHTMLElementsAtPoint()”) is up to you. In iCab Mobile it returns the HREF and SRC attributes of A and IMG tags, so the URLS can be directly processed.

The example doesn’t include the UIActionSheet delegate method which is called when you tab on one of the buttons in the contextual menu. But I think you should already know how to handle this. Also a few other details might be missing, but I think you should be able now to implement your own custom contextual menu for UIWebView objects with the information from the blog post.



















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 occurrencesvar MyApp_SearchResultCount = 0;// helper function, recursively searches in elements and their child nodesfunction 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 searchfunction MyApp_HighlightAllOccurencesOfString(keyword) {  MyApp_RemoveAllHighlights();  MyApp_HighlightAllOccurencesOfStringForElement(document.body, keyword.toLowerCase());}// helper function, recursively removes the highlights in elements and their childsfunction 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 highlightsfunction 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.



0 0