123
votes

I have an app where I load content to a UIWebView and present this. I cannot disable user interaction completely because I want the user to be able to click links. I just need to disable user selection. I found somewhere in the Internets that you can use:

document.body.style.webkitUserSelect='none';

I tried inserting this as

[self.contentView stringByEvaluatingJavaScriptFromString:@"document.body.style.webkitUserSelect='none';"]; 

in webViewDidFinishLoad:

However, it does not work. I am still able to select and copy text inside the WebView.

Any Ideas what might be going wrong?

Update: This only seems to happen starting with iOS 4.3

12

12 Answers

280
votes

Here are a few ways to disable selection:

Add the following to your mobile web documents

<style type="text/css">
* {
    -webkit-touch-callout: none;
    -webkit-user-select: none; /* Disable selection/copy in UIWebView */
}
</style>

Programmatically load the following Javascript code:

NSString * jsCallBack = @"window.getSelection().removeAllRanges();";    
[webView stringByEvaluatingJavaScriptFromString:jsCallBack];

Disable the Copy / Paste user menu:

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender 
{    
    if (action == @selector(copy:) ||
        action == @selector(paste:)||
        action == @selector(cut:)) 
    {
        return _copyCutAndPasteEnabled;
    }
    return [super canPerformAction:action withSender:sender];
}
104
votes

I can confirm that the following code works in iOS 5.0 - 8.0.

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    // Disable user selection
    [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitUserSelect='none';"];
    // Disable callout
    [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitTouchCallout='none';"];
}

Also works for iOS 9 and later. Here's the swift code:

func webViewDidFinishLoad(webView: UIWebView) {
    // Disable user selection
    webView.stringByEvaluatingJavaScriptFromString("document.documentElement.style.webkitUserSelect='none'")!
    // Disable callout
    webView.stringByEvaluatingJavaScriptFromString("document.documentElement.style.webkitTouchCallout='none'")!
}
26
votes

I am using this technique in a web app for Android / iPhone (packaged with Trigger.IO) and found it would only work with the chaining syntax for the :not() pseudo-class, :

*:not(input):not(textarea) {
-webkit-user-select: none; /* disable selection/Copy of UIWebView */
    -webkit-touch-callout: none; /* disable the IOS popup when long-press on a link */

}
18
votes

I like the WrightsCS solution but I will use this so the users can still using the copy,paste and select actions on inputs

<style type="text/css">
*:not(input,textarea) {
    -webkit-touch-callout: none;
    -webkit-user-select: none; /* Disable selection/Copy of UIWebView */
}
</style>
9
votes

I am not sure how the setup is done, but why dont you just clear the pasteBoard when viewWillDisappear is called. Maybe something like in your appDelegate.m:

[UIPasteboard generalPasteboard].string = nil;

this will make sure whatever data user might have copied, they will not be able to paste it outside of the app.

Also, like Engin said you can override the canPerformSelector method in the controller class that contains the uiwebview.

7
votes

TPoschel answer is corrent but in my case order was important.

// this works - locks selection and callout
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitUserSelect='none';"];
    [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitTouchCallout='none';"];
}

// this doesn't work - locks only callout
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitTouchCallout='none';"];
    [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitUserSelect='none';"];
}
7
votes

I can confirm this will definitely work for you.

<style type="text/css">
  *:not(input):not(textarea) {
   -webkit-user-select: none; /* disable selection/Copy of UIWebView */
   -webkit-touch-callout: none; /* disable the IOS popup when long-press on a link */
   }       
</style>

If you want Disable only anchor button tag use this.

    a {-webkit-user-select: none; /* disable selection/Copy of UIWebView */
   -webkit-touch-callout: none; /* disable the IOS popup when long-press on a link */
     }
6
votes
    let longPress:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: nil, action: nil)
    longPress.minimumPressDuration = 0.2
    webView.addGestureRecognizer(longPress)

Simply add this code to your viewDidLoad(). User can click on link but can not copy the content.

5
votes

Result of the great work for one week! All others answers are incorrect if you want to save mouse events and user input on many pages.

1) Swizzle method (by rentzsch/jrswizzle library):

[NSClassFromString(@"UIWebDocumentView") jr_swizzleMethod:@selector(canPerformAction:withSender:) withMethod:@selector(myCanPerformAction:withSender:) error:nil];

NSObject+myCanPerformAction.h:

@interface NSObject (myCanPerformAction)

- (BOOL)myCanPerformAction:(SEL)action withSender:(id)sender;

@end

NSObject+myCanPerformAction.m:

#import "NSObject+myCanPerformAction.h"

@implementation NSObject (myCanPerformAction)

- (BOOL)myCanPerformAction:(SEL)action withSender:(id)sender {
    if (action == @selector(copy:)) {
        return [self myCanPerformAction:action withSender:sender];
    }
    if (action == @selector(paste:)) {
        return [self myCanPerformAction:action withSender:sender];
    }
    return NO;
}

@end

2) Place UIWebView on UIView and add a code:

    UITapGestureRecognizer* singleTap = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)] autorelease];
    singleTap.numberOfTapsRequired = 2;
    singleTap.numberOfTouchesRequired = 1;
    singleTap.delegate = self;
    [self.view addGestureRecognizer:singleTap];

And this one:

- (void)handleSingleTap:(UIGestureRecognizer*)gestureRecognizer {
    return;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    if ([otherGestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
        UITapGestureRecognizer *gesture = (UITapGestureRecognizer *)otherGestureRecognizer;
        if (gesture.numberOfTapsRequired == 2) {
            [otherGestureRecognizer.view removeGestureRecognizer:otherGestureRecognizer];
        }
    }
    return YES;
}
4
votes

The first solution given worked perfectly for me...until I loaded a .pdf into my UIWebView.

Loading a .doc file worked perfectly, but loading a .pdf resulted in the following line of code no longer having the desired effect and the copy/define menu popped up again on a long touch by the user.

    [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitUserSelect='none';"];

After another bout of hair pulling I found this answer on here by Johnny Rockex and it worked like a champ. UIWebView without Copy/Paste when displaying PDF files

Many thanks to him for this easy to implement, genius solution!!

2
votes

For me, I have intended to fetch the images' NSData from UIWebView by LongPressGesture.

But the Magnifier and Copy/Paste/Cut always occur before my func execute.

And I found this: enter image description here

It means, the the Magnifier and Copy/Paste/Cut need 0.5s to execute, so if your func can be executed in 0.49s, DONE !

self.longPressPan.minimumPressDuration = 0.3
-4
votes

Use the web view interection function

   webView.userInteractionEnabled = false

It works for me

PS: remember to enable the interaction back when you want the user could interact with the webview again