Getting and Setting Cursor Position of UITextField and UITextView in Swift
Asked Answered
S

5

165

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.

Salenasalene answered 21/1, 2016 at 11:16 Comment(0)
S
454

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

Salenasalene answered 21/1, 2016 at 11:16 Comment(21)
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?Skiba
@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.Salenasalene
Is there anything to move cursor to right while textfield is empty ? I followed your answer but i could not make it.Oblique
@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?Salenasalene
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.Oblique
@yucelbayram, You can place the cursor after the H using textField.endOfDocument. You could also use underscores or spaces to represent the missing letters.Salenasalene
@Salenasalene 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! :)Backstroke
@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.Salenasalene
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?Generalship
@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.Salenasalene
I've got a janky work around but posted a question incase someone has something better. https://mcmap.net/q/151498/-how-can-i-set-uitextview-selectedtextrange-with-animation-and-scroll-position/563381Generalship
@Salenasalene please help me with this answer too : https://mcmap.net/q/151499/-cursor-shifts-to-end-on-edit-of-formatted-decimal-textfield-swiftOdlo
why is it accepted as the answer? According to it cursor position == 0 and cursor position is undefined are the same.Monaghan
@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.Salenasalene
@Salenasalene 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!Monaghan
why Set cursor position not work in 'textViewShouldBeginEditing' and 'textViewDidBeginEditing'Vaticination
Any idea how to track/set the cursor in a UISearchBar object?Coulomb
@Salenasalene , 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).Backstroke
@ArielSD, I'm sorry, I haven't worked on this for a while. I can't remember.Salenasalene
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.Dipietro
@T.Eswaran, This isn't quite what you're asking, but you can check this out:https://mcmap.net/q/23495/-detecting-taps-on-attributed-text-in-a-uitextview-in-iosSalenasalene
C
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.

Chiasma answered 23/11, 2017 at 23:52 Comment(3)
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.Quickie
@MichaelOzeryansky Yeah, I think so, too. But this begs the question — what IS the right method to be setting cursor position manually?Nicaea
using dispatch queue worked for me too, thanksAnesthetic
O
5
let range = field.selectedTextRange

field.text = "hello world"

field.selectedTextRange = range 
Obliteration answered 24/5, 2019 at 14:39 Comment(0)
H
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.

Heyerdahl answered 13/3, 2019 at 6:7 Comment(1)
@Salenasalene Floating Cursor is one method to set cursor location in textView or textField.Heyerdahl
P
2

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.

Protege answered 25/5, 2021 at 10:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.