Getting and Setting Cursor Position of UITextField and UITextView in Swift
IosSwiftUitextfieldUitextviewUitextpositionIos Problem Overview
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
- https://stackoverflow.com/questions/16432364/getting-the-cursor-position-of-uitextfield-in-ios
- https://stackoverflow.com/questions/1500233/control-cursor-position-in-uitextfield
- https://stackoverflow.com/questions/22507884/uitextfield-get-currently-edited-word
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.
Ios Solutions
Solution 1 - Ios
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 - Ios
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 - Ios
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 - Ios
let range = field.selectedTextRange
field.text = "hello world"
field.selectedTextRange = range
Solution 5 - Ios
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.