160

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.

Community
  • 1
  • 1
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393

5 Answers5

446

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

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • 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? – mfaani Sep 22 '16 at 16:46
  • 2
    @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. – Suragch Sep 22 '16 at 16:56
  • Is there anything to move cursor to right while textfield is empty ? I followed your answer but i could not make it. – Yucel Bayram Jan 25 '17 at 12:15
  • @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? – Suragch Jan 25 '17 at 12:54
  • 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. – Yucel Bayram Jan 25 '17 at 13:37
  • 1
    @yucelbayram, You can place the cursor after the H using `textField.endOfDocument`. You could also use underscores or spaces to represent the missing letters. – Suragch Jan 25 '17 at 14:24
  • @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! :) – Fattie Feb 22 '17 at 19:11
  • @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. – Suragch Feb 23 '17 at 00:03
  • 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? – Ryan Poolos Jul 19 '17 at 18:24
  • @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. – Suragch Jul 19 '17 at 22:43
  • I've got a janky work around but posted a question incase someone has something better. https://stackoverflow.com/q/45216232/563381 – Ryan Poolos Jul 20 '17 at 13:31
  • @Suragch please help me with this answer too : https://stackoverflow.com/a/53475629/2714877 – Mamta Nov 27 '18 at 05:13
  • why is it accepted as the answer? According to it `cursor position == 0` and `cursor position is undefined` are the same. – Vyachaslav Gerchicov Jan 23 '19 at 13:34
  • @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. – Suragch Jan 23 '19 at 17:31
  • @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! – Vyachaslav Gerchicov Jan 24 '19 at 08:49
  • why Set cursor position not work in 'textViewShouldBeginEditing' and 'textViewDidBeginEditing' – amin Feb 09 '19 at 13:49
  • Any idea how to track/set the cursor in a UISearchBar object? – Sevy11 Apr 19 '19 at 17:05
  • @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). – Fattie Jun 25 '19 at 17:42
  • 1
    @ArielSD, I'm sorry, I haven't worked on this for a while. I can't remember. – Suragch Jul 11 '19 at 21:18
  • 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. – T.Eswaran Sep 07 '20 at 15:28
  • @T.Eswaran, This isn't quite what you're asking, but you can check this out:https://stackoverflow.com/a/32262426/3681880 – Suragch Sep 08 '20 at 06:34
79

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.

Adam Bardon
  • 3,829
  • 7
  • 38
  • 73
Michael Ros
  • 1,171
  • 9
  • 9
  • 10
    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. – Michael Ozeryansky Sep 09 '18 at 02:48
  • 2
    @MichaelOzeryansky Yeah, I think so, too. But this begs the question — what IS the right method to be setting cursor position manually? – timetowonder Sep 19 '19 at 12:58
  • using dispatch queue worked for me too, thanks – Panks May 26 '22 at 08:27
4
let range = field.selectedTextRange

field.text = "hello world"

field.selectedTextRange = range 
Ivan
  • 1,320
  • 16
  • 26
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.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
Parth Patel
  • 915
  • 11
  • 33
1

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.

daj mi spokój
  • 248
  • 1
  • 2
  • 8