Skip to content


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.

Posted in iPhone & iPod Touch, Programming.

Tagged with , , , , .


153 Responses

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

  1. Sejin Park says

    Hi~ this is good!!

    but i’m not sure this sentence mean
    —————–
    When initializing the UIWebView delegate, we need to add the delegate as observer for the notification…
    —————–
    Can you help me please?
    thanks. :)

  2. Alexander Alexander says

    @Sejin Par
    The WebView can have a delegate which implements certain methods which are called by the UIWebView object for example to ask if a certain URL should be loaded and to notify the delegate when the page load has finished etc. In my example this delegate should also do the contextual menu stuff for the WebView. And therefore it has to be notified about the tap-and-hold gesture. And because in my example the tap-and-hold gesture is distributed through the Notification Center, the delegate should make it an observer for this “tap-and-hold” notification.

  3. Sejin Park says

    ah~! now i see!

    thank you for your kindness. :)

  4. Michael Studman says

    Excellent stuff and just what I needed. Your solution seems to be the best around. Some additions I made which you may find useful:

    I changed the webkitTouchCallout change to this:

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

    Doing it at the start of loading and on the documentElement instead ensured the UIActionSheets couldn’t pop out even while the page was still loading. If its possible your document may have iframes or framesets you would also want to apply the style to them too e.g. frame.contentDocument.documentElement.style.webkitTouchCallout = ‘none’;

    I think you also need to modify tapAndHoldAction so that it only fires the notification if the long tap is within a UIWebView or its child otherwise, for example, if you caused a popover to display which was within the bounds of the frame of the UIWebView, long tapping on that would cause a second popover to display… and so on! Here’s what worked for me:


    UIView* clickedView = [self hitTest:CGPointMake(tapLocation.x, tapLocation.y) withEvent:nil];
    while (clickedView != nil) {
    if ([clickedView isKindOfClass:[UIWebView class]]) {
    break;
    }
    clickedView = clickedView.superview;
    }

    if (clickedView) {
    NSDictionary *coord = [NSDictionary dictionaryWithObjectsAndKeys:
    [NSNumber numberWithFloat:tapLocation.x],@"x",
    [NSNumber numberWithFloat:tapLocation.y],@"y",nil];
    }

    And finally, again if you expect frames or iframes in the document, you should modify JSTools.js so that it looks within them too. document. elementFromPoint(x, y) will only go down as far as the frame element from which you must start the process again. I’ll post some code for that at a later point.

  5. Alexander Alexander says

    @Michael Studman
    Thanks for your valuable additions.

    According to the frames, I’ve left them out in my examples and also in my previous blog posts because they just make the code more “complicated” and frame support is not really necessary to explain the idea behind the implementation.

    I think it’s not the best idea to do the hit testing in the method “tapAndHoldAction”, because then the Window class has to know about the layout of its subviews. Also it’s not necessarily only a webView which might be interested in these notifications. I would suggest to do the “hit testing” within the objects which are the observers of the tapAndHoldAction notification. But you’re right, I should have mentioned this in the blog post

  6. Michael Studman says

    I can see why you chose not to mention it – it does reduce the clarity! It was urgently necessary for my scenario (because I had a page full of oath widgest with links still showing popovers) so thought others may benefit.

    Good point about where to put the hit tests.

    Keep up the great work!

  7. Beth says

    Thanks so much for this! It was also just what I needed!

  8. Robin says

    Hi,

    Thanks for your post!!

    I am new to iPhone dev. Could you please explain how to actually use it while developing an app or post the source code online.

    Thanks in advance

  9. Alexander Alexander says

    @Robin
    First of all, customizing the contextual menu of UIWebView is a more complex task,so this is not really a good start for someone new to iPhone developement. You have to overwrite a class, creating a category for another, use Notifications, implement delegate methods , write code in Objective C and also in JavaScript.
    Though each of these topics is “basic” stuff, you need some experience to understand how this works together. So if you are new to iPhone programming, you should start with some more simpler tasks first.
    My example code should show a way how to implement certain features, but because the details of these features will be different in each App, it doesn’t make sense to show much more than the basics of the implementation in the blog post. Otherwise the example code would get much larger and more difficult to read. There would be too much stuff included which isn’t really important for the basic idea.

    If need more detailed help, please write an email and please include some more details about your concrete problem.

  10. steve says

    Hello, thanks for this ! It’s working great :)
    I’m using UITabBar, and I had to put the addObserver in the viewWillAppear method and a removeObserver in viewWillDisappear method, so notifications are not handled when UIWebView is not shown !

  11. Ky says

    Alexander,
    Thank you for the great tutorial and the iCab Mobile app is great too. I have one question about this UIWebView stuff. I want to apply some css to a page after it is done loading but it doesn’t seem to work. I made a javascript function to add the style and then invoke the javascript function but nothing happened. It seems like the webview doesn’t apply the newly added style. Do you know why?

    Here is the function:
    function MyFunction_AddCSS()
    {var newstyle = document.createElement(‘style’); newstyle.innerHTML = ‘.fbxWelcomeBoxName {display:block;font-weight:color:#ff0000;normal;padding:4px 0 1px;width:112px;word-wrap:break-word;}’;
    document.head.appendChild(newstyle);
    }

  12. Enzo says

    A great article! Thanks for sharing!

    With document.elementFromPoint(x,y) it is possible to get the elements the user pointed on, but does someone have an idea how I can identify the concrete characters under my tap? A result represented as a Range() object would be great. Thanks.

  13. Alexander Alexander says

    @Ky
    I think, if you create a “style” element you can not set the CSS properties using “innerHTML”.
    You have to add the “style” element to the “head” section of the document and then use the “insertRule()” funtion of the style object to add your stylesheet rules.

    Something like this should work:

    var style = document.createElement(“style”);
    document.getElementsByTagName(“head”)[0].appendChild(style);
    var count = document.styleSheets.length-1;
    document.styleSheets[count-1].insertRule(
    “.fbxWelcomeBoxName {display:block;…}”);

  14. Alexander Alexander says

    @Enzo
    Sorry, but I don’t know if you can directly access the character at a certain coordinate very easy. In case you can work with the selected text (in case there’s a text selection), you may use “window.getSelection()” to get the text that is selected.

  15. shetainniseri says

    Hi Iam Prabhu from chennai,joined today in this forum… :)

  16. Ky says

    @Alexander
    Thank you for your reply. I try your code but it is still not working. Can you check if the function I use have any syntax error or anything? Really appreciate your help.

    - (void)addCSS:(UIWebView*)webView
    {
    NSString *testCode = @”.fbxWelcomeBoxName {display:block;font-weight:color:#ff0000;normal;padding:4px 0 1px;width:112px;word-wrap:break-word;}”;
    NSString *cssCode = [NSString stringWithFormat:@"function Tektrify_AddCSS() \
    {var style = document.createElement(\"style\"); \
    document.getElementsByTagName(\"head\")[0].appendChild(style); \
    var count = document.styleSheets.length-1; \
    document.styleSheets[count-1].insertRule(\”%@\”);}”,testCode];
    [webView stringByEvaluatingJavaScriptFromString:cssCode];
    [webView stringByEvaluatingJavaScriptFromString:@"Tektrify_AddCSS()"];
    }

  17. Alexander Alexander says

    @Ky
    At least the “font-weight” CSS property is incomplete.

    And There was a small typo in my code as well (sorry for that). In the line
    var count = document.styleSheets.length-1;
    the “-1″ must be deleted. In the next line where the “count” is used, it is already decremented.

  18. Ky says

    @Alexander
    those changes work!! Thanks a bunch for the quick reply. I have one more question if you don’t mine. I try googling around but your blog is the most usefully WebView blog out there so. Now I’m trying to load in the whole .css file that contains a bunch of “.fb…etc”. I try using the same code and just replace that one style with the whole content of the css file but it doesn’t work. From the code above it seems that what we are creating is just one style. I’m have very little html knowledge so if I sound like a dumb, please forgive me.

    - (void)addCSS:(UIWebView*)webView
    {
    NSString *path = [[NSBundle mainBundle] pathForResource:@”MyCSS” ofType:@”css”];
    NSString *css = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    NSString *cssCode = [NSString stringWithFormat:@"function Tektrify_AddCSS() \
    {var style = document.createElement(\"style\"); \
    document.getElementsByTagName(\"head\")[0].appendChild(style); \
    var count = document.styleSheets.length; \
    document.styleSheets[count-1].insertRule(\”%@\”);}”,css];
    [webView stringByEvaluatingJavaScriptFromString:cssCode];
    [webView stringByEvaluatingJavaScriptFromString:@"Tektrify_AddCSS()"];
    }

  19. Alexander Alexander says

    Yes, the above code will only add a single CSS rule. If you want to add a whole CSS file, you should better create a LINK element where you can directly link to the CSS file in the web or even link to a CSS file within your App bundle. And then adding the LINK element to your HTML code. You have to create three attributes for the LINK tag:

    <link rel=”stylesheet” type=”text/css” href=”url_of_stylesheet.css”>

  20. Enzo says

    @Alexander

    I found a workaround for getting the exact character position by adding a SPAN around each character ;) That’s a little bit of an overkill, but works fast enough. On iOS >= 4.0 you can use the following Range() property to narrow the position: range. getBoundingClientRect().

    @Ky

    This one works too and respects the DOMReady state (‘css’ variable contains the styles, but remove line feeds and quote correctly before):

    id js = [NSString stringWithFormat:@"function hoDomReady(){if(/loaded|complete/.test(document.readyState)){var styleTag=document.createElement('style'); styleTag.setAttribute('type','text/css'); styleTag.textContent='%@'; document.body.appendChild(styleTag); } else { window.setTimeout(hoDomReady, 50)}}; hoDomReady();", css];
    [webView_ stringByEvaluatingJavaScriptFromString:js];

    Thanks for your help.

  21. tiantian says

    Enlightening article!
    I wonder is there a similar to customise the UIMenuController shows up when hold your finger on a simple text in a UIWebView. I set the “menuItems” property of the [UIMenuController sharedMenuControlle] in awakeFromNib. Howerver, it shows up with a “More” item after the default system item “COPY”, i had to tap the “More” item to see my custom items. I want to show my custom items directly.

  22. Alexander Alexander says

    @tiantian
    The next release of iCab Mobile will add a “Search” item to the menu of UIMenuController which allows to search for the text selection of the UIWebView at google (or other search engines). This is the only menu item I add to this menu and it is directly shown, there’s no “more” item. I assume that the “more” item will be shown if there are too many custom items (though I don’t know how many is “too many”). In my case there’s only one single custom item and this is shown directly.

  23. Karna says

    I have added the MyWindows h and m class in my code.But the method – (void)sendEvent:(UIEvent *)event is not called. I am not sure how to call the method when the webview is touched

  24. Alexander Alexander says

    @Karna
    As I’ve written in the blog post, you have to change the class of the window object in the main NIB file from UIWindow to MyWindow in Interface builder. This is important because normally the window object is automatically created for you when the main NIB file is loaded. And therefore the information that the window object should be of the MyWindow class must be present in the NIB file.

  25. Jerry Walton says

    can this technique be used to “customize” the UIMenuView that pops up when the user makes a selection (selecting text) on the UiWebVIew?

    i know that you can programmatically add buttons to the menu.
    the app designer wants to add different images for the menu buttons (Copy, Paste)

  26. Alexander Alexander says

    @Jerry Walton
    AFAIK you can only add new buttons but you can not modify the existing ones.

  27. Jerry Walton says

    Alexander,

    OK, now I am confused about the capabilities of your example here.

    Does your example provide the ability to “intercept” the element selection
    routine of the UIWebView (event generated when the user selects text or image)
    and allow to programmatically display a custom pop-up?

    The client for the iPad application I am developing would prefer to have “stylized”
    Copy, Paste menu (different font, different background, etc.,)?

    Can this be done with your example code?

    Thanks for your response and sharing your knowledge. ;)

  28. Jerry Walton says

    Alexander,

    I got it figured out. Thanks so much for the enlightenment.

    God Bless.

    Jerry

  29. Alexander Alexander says

    @Jerry Walton
    The Copy&Paste menu (text selection) and the contextual menu for links (tap&hold on links and images) are completely different topics.
    This blog post covers only the contextual menu.

  30. Xfight says

    Really thanks !!
    Works like a charm

  31. Atrox says

    Could someone provide full sample code … I don’t get it.

  32. Polo says

    I am doing a project may need your help! Which is how to program in a UIWebView word search, because webview shield the click event, so I do not know how to do! Can you give me an example of the code ! I really need! Sincerely hope you can help me! Thank you!

  33. Polo says

    I am doing a project may need your help! Which is how to program in a UIWebView word search, because webview shield the click event, so I do not know how to do! Can you give me an example of the code ! I really need! Sincerely hope you can help me! Thank you!my email is “sixi0224@126.com”

  34. Alexander Alexander says

    @Polo
    I think you can find my blog post “Search and highlight text in UIWebView” interesting, where I’ve shown how to do a search in UIWebView.

  35. Polo says

    Your article I have read carefully, but there are a few questions of my program, I hope you can help me solve it! I sincerely thank you! First of all, after i use your code “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;
    }”,
    returned “tags” is “, BODY, HTML,”, and not I select the string, may I ask how is this going? Thank you!

  36. Alexander Alexander says

    This code is only meant as an example, so depending of the tasks you want to accomplish, you have to do different things here. In my example there’s just a list of HTML elements returned, which are located at the location where you’ve touched the screen. And HTML elements are not accurate enough to tell you the exact text, word or even letter, you’ve touched. If you only need to get the URL of a link, you only need to know if an “A” element was hit, find this element and get the value of the HREF element. If you need to know more, you have to collect more data.
    If you need to get the selected text, then you probably have to use a totally different approach.

  37. Prasad says

    Thank you very much Alexander, helped me lot.

  38. Programmer says

    Hi Alex,

    It seems that if the content of the uiwebview is an external link(certain websites), using
    [code]
    - (void)webViewDidStartLoad:(UIWebView *)webView {
    [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitTouchCallout='none';"];
    }
    [/code]

    still doesn’t block uiactionsheet from popping up. Any idea how to prevent it from showing during the page is still loading? It works well when

    [code]
    - (void)webViewDidFinishLoad:(UIWebView *)webView
    [/code]

  39. Alexander Alexander says

    @Programmer
    The method “webViewDidStartLoad” is called before any data of the web page is received. if you set the “-webkit-touch-callout” css rule here, this rule might be overwritten again when the HTML source is received and the WebView can create the final web page elements and the documentElement for the page. All the elements will then get their default stylesheets attached, as they are defined in the web page. And your own css rule is overwritten and lost.
    Using “webViewDidStartLoad” might work in a few simple cases where the page is loaded so fast, that the documentElement is already created when you finally set the “-webkit-touch-callout” css rule (UIWebView is doing the rending in its own thread running in the background, so this might be possible). But there’s no garantee for this and for more complex web pages, you have to do this in “webViewDidFinishLoad:” to be sure that it really works.

  40. Programmer says

    I am writing a browser, so I wouldn’t know which website user is viewing. For this case, do you suggest that i use the “hacking” way where extending uiwebview or a uiview on top of uiwebview solution? This is because user will start tap and hold to save images, or any other media files before a web page is loaded.

  41. Alexander Alexander says

    @Programmer
    Adding a layer on top of the UIWebView can work to easily block the default contextual menu, but of course this has also some limitations: you’ll also block all other gestures as well.
    So probably, this isn’t a general solution.

  42. Programmer says

    Found out some of the anchor that are generated during runtime by javascript does not get intercepted with this approach.

  43. Alexander Alexander says

    @Programmer
    If these dynamically created elements are created before the contextual menu opens and when they are visible and correctly inserted into the HTML/DOM tree, they should be found.

  44. xiang.gao says

    hi,Alexander
    Thank you for sharing it,It’s very useful for me.
    But I have a question,I do it like your article,But when I select a text in the textfield(),the shown menu has “Paste” ,but I want to change it ,how can I do that?

  45. Tristan says

    Amazing work, Alexander. This is a very clever solution to the problem. You mention earlier you in your post that it’s possible to do this in iOS 3.2 with a UIGestureRecognizer instead. Do you have sample code as to how to do this?

    Thanks!

  46. Alexander Alexander says

    @xiang.gao
    The Copy & Paste menu is something different and can not be addressed by the method described in this blog post. To customize the Copy & Paste menu you need to use the “UIMenuController” class. Using this class, you can add new items to this menu. I’ll probably write a blog post about this topic in the future.

  47. Alexander Alexander says

    @Tristan
    I haven’t done this with UIGestureRecognizer for this use case yet. I’m using UIGestureRecognizer only for other stuff yet, where the compatibility with iOS 3.1 isn’t critical. I’m even no longer sure if this approach would work for UIWebView, because internally the UIWebView might already “steal” all the gestures for itself….

    For “tap-and-hold” there’s already a special class “UILongPressGestureRecognizer” available. So in general you only need to create an object of this class, and then set this object for the view which should respond to the gesture using the method “setGestureRecognizers:” (this method is defined for all UIView objects).

  48. xiang.gao says

    The uiwebview’s Copy & Paste menu is in English, I just want to change it to Chinese,Can I do that without custom the Copy & Paste menu?

  49. Alexander Alexander says

    @xiang.gao
    If the language of this menu is not in Chinese when the system language and the App itself is in Chinese, then I think you can only change this by customize the menu yourself. Normally this menu should be in the same language as the App. And the App language is chosen based on the system language (if the App does not contain resources for the system language, then it’s using the english resources instead and the App will be in English).
    I guess that this is also true for Chinese, so if this menu is not in Chinese in your App, then maybe you haven’t explicitly added Chinese resources yet. It’s important to do this properly because otherwise the languages of the system elements and your own custom elements won’t match.

  50. xiang.gao says

    Thank you very much ,Alexander.
    I will try to add Chinese resources first.

1 2 3 4



Some HTML is OK

or, reply to this post via trackback.