Getting and Setting Cursor Position of UITextField and UITextView in Swift

114,318

Solution 1

The following content applies to both UITextField and UITextView.

Useful information

The very beginning of the text field text:

let startPosition: UITextPosition = textField.beginningOfDocument

The very end of the text field text:

let endPosition: UITextPosition = textField.endOfDocument

The currently selected range:

let selectedRange: UITextRange? = textField.selectedTextRange

Get cursor position

if let selectedRange = textField.selectedTextRange {

    let cursorPosition = textField.offset(from: textField.beginningOfDocument, to: selectedRange.start)

    print("\(cursorPosition)")
}

Set cursor position

In order to set the position, all of these methods are actually setting a range with the same start and end values.

To the beginning

let newPosition = textField.beginningOfDocument
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)

To the end

let newPosition = textField.endOfDocument
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)

To one position to the left of the current cursor position

// only if there is a currently selected range
if let selectedRange = textField.selectedTextRange {

    // and only if the new position is valid
    if let newPosition = textField.position(from: selectedRange.start, offset: -1) {

        // set the new position
        textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
    }
}

To an arbitrary position

Start at the beginning and move 5 characters to the right.

let arbitraryValue: Int = 5
if let newPosition = textField.position(from: textField.beginningOfDocument, offset: arbitraryValue) {

    textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
}

Related

Select all text

textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.endOfDocument)

Select a range of text

// Range: 3 to 7
let startPosition = textField.position(from: textField.beginningOfDocument, offset: 3)
let endPosition = textField.position(from: textField.beginningOfDocument, offset: 7)

if startPosition != nil && endPosition != nil {
    textField.selectedTextRange = textField.textRange(from: startPosition!, to: endPosition!)
}

Insert text at the current cursor position

textField.insertText("Hello")

Notes

  • Use textField.becomeFirstResponder() to give focus to the text field and make the keyboard appear.

  • See this answer for how to get the text at some range.

See also

Solution 2

In my case I had to use DispatchQueue:

func textViewDidBeginEditing(_ textView: UITextView) {
   DispatchQueue.main.async {
      textField.selectedTextRange = ...
   }
}

Nothing else from this and other threads worked.

PS: I double checked which thread did textViewDidBeginEditing was running on, and it was main thread, as all UI should run on, so not sure why that little delay using main.asynch worked.

Solution 3

For set cursor position at your point:

textView.beginFloatingCursor(at: CGPoint(x: 10.0, y: 10.0))

For reset cursor position:

textView.endFloatingCursor()

Note: This example works in both Textview & Textfield.

Solution 4

let range = field.selectedTextRange

field.text = "hello world"

field.selectedTextRange = range 

Solution 5

I hadn't even noticed that the textView in one of my published apps actually starts from the left after you delete a word on the top line of the textView. Not only was the caret going to the left but the text would then start from the left where the caret was when you typed something! It wouldn't do it in a middle line or the bottom line, only when the line was deleted on the top.

func textViewDidChange(_ textView: UITextView)
{
   if myTextView.text!.count > 0
   {
      myTextView.textAlignment = .center
   }
}

The caret still goes to the left which is not ideal but at least with that 'if' statement added, it will only add the text from the centre as intended and won't add the text from the left.

Share:
114,318
Suragch
Author by

Suragch

Read my story here: Programming was my god

Updated on May 26, 2021

Comments

  • Suragch
    Suragch almost 3 years

    I've been experimenting with UITextField and how to work with it's cursor position. I've found a number of relation Objective-C answers, as in

    But since I am working with Swift, I wanted to learn how to get the current cursor location and also set it in Swift.

    The answer below is the the result of my experimentation and translation from Objective-C.

  • mfaani
    mfaani over 7 years
    What is the purpose of startPosition? What can you do with it? Also what is the purpose that one might need to get the cursor position?
  • Suragch
    Suragch over 7 years
    @Honey, I used that variable name to refer to two different things here. The first was to refer to the beginning of the text field. It would be useful if you wanted to move the cursor there. The second was to refer to the begging of a selected range of text. It is useful if you want to copy that range or set some attribute on it.
  • Yucel Bayram
    Yucel Bayram over 7 years
    Is there anything to move cursor to right while textfield is empty ? I followed your answer but i could not make it.
  • Suragch
    Suragch over 7 years
    @yucelbayram, If the text field is empty, then there is nowhere to move the cursor to. The cursor can only move through text. What are you trying to do?
  • Yucel Bayram
    Yucel Bayram over 7 years
    Oh ok i figured it out. But you have an idea about that one ? For example its a text with 5 letters, you dont know what is this, but first letter is given. Just like a H_ _ _ _ , (it is "HELLO"). When user start to type, I want to cursor move to second index and skip the first one(H letter.). If this is out of topic, its fine. Thanks anyway.
  • Suragch
    Suragch over 7 years
    @yucelbayram, You can place the cursor after the H using textField.endOfDocument. You could also use underscores or spaces to represent the missing letters.
  • Fattie
    Fattie about 7 years
    @Suragch thanks for another great canonical answer. An ultimate addition would be get pixel position of cursor in iOS10 (there are many messy, archaic, posts on this issue). And a "last-word" addition would be the converse, find cursor character position .. given a pixel position! :)
  • Suragch
    Suragch about 7 years
    @JoeBlow, yes, that would be a useful addition. This is something I will have to learn in the future, too, because I want to make a custom text view that will draw its own cursor.
  • Ryan Poolos
    Ryan Poolos almost 7 years
    Is it possible to set the selectedTextRange in an animated fashion so that if its placed at the bottom you get a typical smooth scroll like setting the contentOffset?
  • Suragch
    Suragch almost 7 years
    @RyanPoolos, I'm guessing it is possible, but I don't know how to do it. This would make a good new question. Link to it if you do.
  • Ryan Poolos
    Ryan Poolos almost 7 years
    I've got a janky work around but posted a question incase someone has something better. stackoverflow.com/q/45216232/563381
  • Michael Ozeryansky
    Michael Ozeryansky over 5 years
    It's not a delay but the next run loop. Most likely the textfield's selected range is being modified by the same function that called textViewDidBeginEditing. So by placing it on the queue, it will happen after the caller has finished.
  • Mamta
    Mamta over 5 years
    @Suragch please help me with this answer too : stackoverflow.com/a/53475629/2714877
  • Vyachaslav Gerchicov
    Vyachaslav Gerchicov over 5 years
    why is it accepted as the answer? According to it cursor position == 0 and cursor position is undefined are the same.
  • Suragch
    Suragch over 5 years
    @VyachaslavGerchicov, Sorry, I don't understand your comment. Although UITextPosition is not exactly the same as an integer index, for plain text it works out to be the same as the offset from the beginning of the document. The cursor position at 0 means it is at the start of the text. It is not undefined. A negative value would be undefined.
  • Vyachaslav Gerchicov
    Vyachaslav Gerchicov over 5 years
    @Suragch I want to create something like phone keypad screen from the native app. So when cursor position == 0 backspace button does nothing but if cursor position is undefined backspace removes the last symbol. In your answer these cases are mixed because I always get zero value even when its position is undefined. The only solution I found is additional checking of isFirstResponder property but it is not even mentioned in your answer!
  • amin
    amin over 5 years
    why Set cursor position not work in 'textViewShouldBeginEditing' and 'textViewDidBeginEditing'
  • Parth Patel
    Parth Patel about 5 years
    @Suragch Floating Cursor is one method to set cursor location in textView or textField.
  • Sevy11
    Sevy11 about 5 years
    Any idea how to track/set the cursor in a UISearchBar object?
  • Fattie
    Fattie almost 5 years
    @Suragch , I've got one for you. A thing is to change the keyboar style, as you enter. Example. Bank numbers are: "First two characters must be A-Z; the rest must be digits". Readers want to know how to reliably change the keybard style (in the example, between ascii and digits) and what to do so on (IMO, the cursor position).
  • Suragch
    Suragch almost 5 years
    @ArielSD, I'm sorry, I haven't worked on this for a while. I can't remember.
  • timetowonder
    timetowonder over 4 years
    @MichaelOzeryansky Yeah, I think so, too. But this begs the question — what IS the right method to be setting cursor position manually?
  • T.Eswaran
    T.Eswaran over 3 years
    Hi All, I have one small doubt. How to get the click position when clicking a specific character after entering some characters in the UITextField.
  • Suragch
    Suragch over 3 years
    @T.Eswaran, This isn't quite what you're asking, but you can check this out:stackoverflow.com/a/32262426/3681880
  • Panks
    Panks almost 2 years
    using dispatch queue worked for me too, thanks