48

I'm having a hard time getting the UITextView to disable the selecting of the text.

I've tried:

canCancelContentTouches = YES;

I've tried subclassing and overwriting:

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender   

(But that gets called only After the selection)

- (BOOL)touchesShouldCancelInContentView:(UIView *)view;  

(I don't see that getting fired at all)

- (BOOL)touchesShouldBegin:(NSSet *)touches
                 withEvent:(UIEvent *)event
             inContentView:(UIView *)view; 

(I don't see that getting fired either)

What am I missing?

Cœur
  • 37,241
  • 25
  • 195
  • 267
dizy
  • 7,951
  • 10
  • 53
  • 54
  • 2
    Since it's impossible to add answers to this: note the BEST answer here (IMHO) is actually a comment by Alexander: since iOS 7 there is a @property(nonatomic,getter=isSelectable) BOOL selectable NS_AVAILABLE_IOS(7_0) – Epaga Feb 23 '14 at 17:14
  • I got the question reopened and added `selectable` as a proper answer. Yay! – Heath Borders Feb 11 '16 at 20:34

11 Answers11

76

Issue How disable Copy, Cut, Select, Select All in UITextView has a workable solution to this that I've just implemented and verified:

Subclass UITextView and overwrite canBecomeFirstResponder:

- (BOOL)canBecomeFirstResponder {
    return NO;
}

Note that this disables links and other tappable text content.

Community
  • 1
  • 1
Dafydd Williams
  • 1,232
  • 12
  • 14
  • 1
    If you don't want any user interaction at all, yes. – Dafydd Williams Oct 02 '12 at 00:46
  • good point. However it also disables the input cursor. Is there a way of allowing the cursor, but not allowing text selection? – jowie Feb 26 '13 at 16:34
  • You can also avoid the subclassing step and just use a category in the same UIViewController where your UITextView lives. (I wouldn't bet on this working forever though) (a)implementation UITextView (NoSelection) - (BOOL)canBecomeFirstResponder { return NO; } (a)end – rob5408 Mar 21 '13 at 17:23
  • 11
    Have you tried to use the property selectable? @property(nonatomic,getter=isSelectable) BOOL selectable NS_AVAILABLE_IOS(7_0); // toggle selectability, which controls the ability of the user to select content and interact with URLs & attachments – Alexander Dec 02 '13 at 15:56
  • That's very useful, @Alexander - thanks! canBecomeFirstResponder is still a good solution if you need to target iOS versions prior to 7, though. – Dafydd Williams Dec 05 '13 at 22:50
  • 4
    This disables links, etc. – junglecat Feb 28 '14 at 17:12
  • A good point. In my case, I wanted links disabled, so I wasn't too fussed about that. – Dafydd Williams Mar 21 '14 at 00:18
  • 7
    Anyone know of a solution that does not disable links but does disable highlighting? I tried overriding long-press but that seems to disable link tapping as well... – Trespassers W May 15 '14 at 22:51
  • 5
    @TrespassersW overwrite - (void)textViewDidChangeSelection:(UITextView *)textView and set textView.selectedRange = NSMakeRange(0, 0); can accomplish this – Pepsin Feb 23 '15 at 10:32
  • seems like in iOS 10, this code disable selection and keep links & scrolling working at same time – He Yifei 何一非 Jan 14 '17 at 07:47
  • Even when disabling selection, user can still long-press and 'drag away' the url. In cases where this was meant to act as a button (and the URL, private), this behavior sure hammers in the final nail. – bauerMusic Feb 12 '19 at 15:35
47

I've found that calling

[textView setUserInteractionEnabled:NO];

works quite well.

Charles
  • 50,943
  • 13
  • 104
  • 142
James M
  • 543
  • 4
  • 3
21

UITextView's selectable property:

This property controls the ability of the user to select content and interact with URLs and text attachments. The default value is YES.

Heath Borders
  • 30,998
  • 16
  • 147
  • 256
  • It seems like there is no setter in objective-C? – ArielSD May 08 '19 at 15:23
  • `-[UITextView isSelectable]`: https://developer.apple.com/documentation/uikit/uitextview/1618627-selectable?language=objc – Heath Borders May 08 '19 at 20:18
  • That looks like just the `get` - I'm seeing compiler errors when trying to set? `textView.isSelectable = NO;` – ArielSD May 08 '19 at 20:20
  • That's because `-isSelectable` is the name of the getter only. Use `-[UITextView setSelectable:]` to set it. With property syntax, both the getter and setter are `selectable`. `BOOL foo = textView.selectable;` or `textView.selectable = bar;` – Heath Borders May 10 '19 at 05:14
  • This one also disables links – Mellao Sep 03 '21 at 08:29
15

Swift 4, Xcode 10

This solution will

  • disable highlighting
  • enable tapping links
  • allow scrolling

Make sure you set the delegate to YourViewController

yourTextView.delegate = yourViewControllerInstance

Then

extension YourViewController: UITextViewDelegate {

    func textViewDidChangeSelection(_ textView: UITextView) {
        if #available(iOS 13, *) {
            textView.selectedTextRange = nil
        } else {
            view.endEditing(true)
        }
    }

}
Roman Podymov
  • 4,168
  • 4
  • 30
  • 57
Ted
  • 22,696
  • 11
  • 95
  • 109
11

Swift 4, Xcode 10:

If you want to make it so the user isn't able to select or edit the text.

This makes it so it can not be edited:

textView.isEditable = false

This disables all user interaction:

textView.isUserInteractionEnabled = false

This makes it so that you can't select it. Meaning it will not show the edit or paste options. I think this is what you are looking for.

textView.isSelectable = false
aaronmbmorse
  • 359
  • 3
  • 10
9

It sounds like what you actually want is a giant UILabel inside a UIScrollView, and not a UITextView.

update: if you are on newer versions of iOS UILabel now has a lines property:

Multiple lines of text in UILabel

Community
  • 1
  • 1
slf
  • 22,595
  • 11
  • 77
  • 101
  • 1
    Yea I guess. Will be the next thing I do if I don't figure this out. – dizy Oct 28 '09 at 21:06
  • thanx dude, I don't know about dizy, but I actually needed a UILabel. – Ilya Saunkin Mar 10 '11 at 12:50
  • UILabel will prevent the line-breaks. You want a UITextView with no user interaction enabled... and then you want that inside of a UIScrollView. Alternatively, you could display a transparent UIWebView with your text in a textView in the UIWebView (just use loadHTML to import your text/code into the UIWebView (including the CSS code to make the background transparent) then you can use javascript to make it not selectable but still scrollable). – Albert Renshaw May 01 '13 at 16:18
  • 1
    @AlbertRenshaw I don't think your first sentence is correct. A quick Google seems to indicate that UILabel supports line breaks just fine. – Mark Amery Aug 22 '13 at 11:59
  • @MarkAmery Wow! I never knew! UILabel has a "lines" property that is on "1" by default... so if you put in multiple lines nothing will happen, you have to modify the "lines" property to a higher number to support multiple lines! Neat :) – Albert Renshaw Aug 22 '13 at 15:48
  • 2
    @AlbertRenshaw Don't make it higher - set it to 0 instead, and then you can have as many lines as you like. – Mark Amery Aug 22 '13 at 16:37
  • @MarkAmery you just made my day! Thankyou for the tip, that helps so much with future projects! Haha! :) +1 – Albert Renshaw Aug 23 '13 at 18:08
5

If you just want to prevent it from being edited, then set the UITextView's "editable" property to NO/False.

If you're trying to leave it editable but not selectable, that's going to be tricky. You might need to create a hidden textview that the user can type into and then have UITextView observe that hidden textview and populate itself with the textview's text.

TechZen
  • 64,370
  • 15
  • 118
  • 145
3

To do this first subclass the UITextView

and in the implementation do the following

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
    self.selectable = NO;
}

- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
        self.selectable = YES;
 }

this should work fine,

SadCS
  • 61
  • 5
1

Did you try setting userInteractionEnabled to NO for your UITextView? But you'd lose scrolling too.

If you need scrolling, which is probably why you used a UITextView and not a UILabel, then you need to do more work. You'll probably have to override canPerformAction:withSender: to return NO for actions that you don't want to allow:

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
    switch (action) {
        case @selector(paste:):
        case @selector(copy:):
        case @selector(cut:):
        case @selector(cut:):
        case @selector(select:):
        case @selector(selectAll:):
        return NO;
    }
    return [super canPerformAction:action withSender:sender];
}

For more, UIResponderStandardEditActions .

mahboudz
  • 39,196
  • 16
  • 97
  • 124
  • 2
    I mentioned that I already tried that :) canPerform doesn't get called until after you make the selection – dizy Oct 29 '09 at 04:06
  • Downvote: this won't prevent the cut/copy/define toolbar from appearing - there are better solutions listed on this page. – Gerard Mar 29 '16 at 10:00
1

You can disable text selection by subclassing UITextView.

The below solution is:

/// Class to disallow text selection
/// while keeping support for loupe/magnifier and scrolling
/// https://stackoverflow.com/a/49428248/1033581
class UnselectableTextView: UITextView {

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        commonInit()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }
    private func commonInit() {
        // prevents selection from loupe/magnifier (_UITextSelectionForceGesture), multi tap, tap and a half, etc.
        // without losing the loupe/magnifier or scrolling
        // but we lose taps on links
        addSubview(transparentOverlayView)
    }
    let transparentOverlayView: UIView = {
        $0.backgroundColor = .clear
        $0.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        return $0
    }(UIView())
    override var contentSize: CGSize {
        didSet {
            transparentOverlayView.frame = CGRect(origin: .zero, size: contentSize)
        }
    }

    // required to prevent blue background selection from any situation
    override var selectedTextRange: UITextRange? {
        get { return nil }
        set {}
    }
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
0

For swift, there is a property called "isSelectable" and its by default assign to true

you can use it as follow:

textView.isSelectable = false