How to set cursor position for UITextView on user input?

22,071

Solution 1

So I ended up adding a UILabel over the UITextView which acts as a placeholder for the textView. Tapping on the UILabel would send the action down to the textView and becomeFirstResponder. Once you start typing, make the label hidden.

Solution 2

The property selectedRange can not be assigned at "any place", to make it work you have to implement the method - (void)textViewDidChangeSelection:(UITextView *)textView, in your case:

- (void)textViewDidChangeSelection:(UITextView *)textView
{
    [textView setSelectedRange:NSMakeRange(0, 0)];
}

you will have to detect when the user is beginning editing or selecting text

Solution 3

My solution:

- (void) viewDidLoad {
    UITextView *textView = [[UITextView alloc] initWithFrame: CGRectMake(0, 0, 200, 200)];
    textView.text = @"This is a test";
    [self.view addSubview: textView];
    textView.delegate = self;
    [textView release];
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(tapped:)];
    [textView addGestureRecognizer: tap];
    [tap release];
}
- (void) tapped: (UITapGestureRecognizer *) tap {
    [textView becomeFirstResponder];
} 

- (void) textViewDidBeginEditing:(UITextView *)textView {
    textView.selectedRange = NSMakeRange(0, 0);
}

I guess it's UITextView internal mechanism to set the cursor when user taps on it. We need to override that by attaching a tap gesture recognizer and call becomeFirstResponder instead.

Solution 4

I was facing the same issue - basically there's a delay when becoming first responder that doesn't allow you to change selectedRange in any of textView*BeginEditing: methods. If you try to delay the setSelectedRange: (let's say with performSelector:withObject:afterDelay:) it shows ugly jerk.

The solution is actually pretty simple - checking order of delegate methods gives you the hint:

  1. textViewShouldBeginEditing:
  2. textViewDidBeginEditing:
  3. textViewDidChangeSelection:

Setting selectedRange in the last method (3) does the trick, you just need to make sure you reposition the cursor only for the first time when the UITextView becomes first responder as the method (3) is called every time you update the content.

A BOOL variable set in shouldChangeTextInRange: one of the methods (1), (2) and check for the variable in (3) should do the trick ... just don't forget to reset the variable after the reposition to avoid constant cursor reset :).

Hope it helps!

EDIT

After few rounds of testing I decided to set the BOOL flag in shouldChangeTextInRange: instead of (2) or (3) as it proved to be more versatile. See my code:

@interface MyClass
{
    /** A flag to determine whether caret should be positioned (YES - don't position caret; NO - move caret to beginning). */
    BOOL _isContentGenerated;
}

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    // deleting
    if([text length] == 0)
    {
        // deleting last character
        if(range.length == [[textView text] length])
        {
            // reached beginning
            /** 
             code to show placeholder and reset caret to the beginning 
            */
            _isContentGenerated = NO;
        }
    }
    else
    {
        // adding
        if(range.location == 0)
        {
            /** 
             code to hide placeholder
            */
            _isContentGenerated = YES;
        }
    }
    return YES;
}

- (void)textViewDidChangeSelection:(UITextView *)textView
{
    if(!_isContentGenerated)
    {
        [textView setSelectedRange:NSMakeRange(0, 0)];
    }
}

Solution 5

I haven't worked enough with that to help you fully, but what happens when you try to play with different selectedRanges? Say, if you do [... setSelectedRange:[NSMakeRange(0,1)]] or [... setSelectedRange:[NSMakeRange(1,0)]]? Does it move the cursor anywhere?

Share:
22,071
Legolas
Author by

Legolas

I love the science behind learning.

Updated on December 28, 2020

Comments

  • Legolas
    Legolas over 3 years

    I am looking for a simple answer for this problem...

    I have a UITextView in which the user can start typing and click on DONE and resign the keyboard.

    When the wants to edit it again, I want the cursor (the blinking line) to be at the first position of the textView, not at the end of textView. (act like a placeholder)

    I tried setSelectedRange with NSMakeRange(0,0) on textViewDidBeginEditing, but it does not work.

    More Info:

    It can be seen that.. when the user taps on the textView the cursor comes up at the position where the user taps on the textView.

    I want it to always blink at starting position when textViewDidBeginEditing.

  • Legolas
    Legolas about 12 years
    Hm. It's just like how I have stated in my question. It does not work. Cursor position = position of User Tap.
  • Argent
    Argent about 12 years
    If it doesn't change the behavior, it sounds like it doesn't call your setSelectedRange at all. Put a breakpoint on the call and follow it through?
  • Legolas
    Legolas about 12 years
    It does call it. My concern is that - that does not fix the issue.
  • Legolas
    Legolas about 12 years
    The link you posted in invalid - Unknown Paste ID!
  • fatuhoku
    fatuhoku almost 10 years
    Seems incredibly hacky. Why not a UIView instead of a UILabel?
  • Andy Poes
    Andy Poes over 9 years
    This is the best solution I've come across yet. This should really be the top answer!
  • ghr
    ghr over 8 years
    @fatuhoku: '...UILabel over the UITextView which acts as a placeholder for the textView'
  • meaning-matters
    meaning-matters over 8 years
    Great answer, but could be improved by adding code.
  • David Jirman
    David Jirman over 8 years
    @meaning-matters Now, that's what I call Christmas :D Thanks!
  • Mike Critchley
    Mike Critchley over 8 years
    Worked like a charm! Thanks!
  • Elliot Blackburn
    Elliot Blackburn over 8 years
    This isn't related to the question, this just shows how to create an inset thus moving the text position within the textview.
  • Jacopo Penzo
    Jacopo Penzo over 7 years
    Legend! Searching on stack overflow is always faster than read the doc :)
  • Ramani Hitesh
    Ramani Hitesh almost 6 years
    Hello, One help my code not display input cursor using custom keyboard ?