Swift
iOS
UITextField
UITextView
Cursor Position

Getting and Setting Cursor Position of UITextField and UITextView in Swift

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In UIKit, cursor position is not exposed as a plain integer property. UITextField and UITextView work through the UITextInput API, which represents caret and selection positions with UITextPosition and UITextRange. Once you understand that model, reading and updating the caret becomes predictable and reusable.

Read the Current Cursor Position

The usual pattern is to read selectedTextRange and measure the offset from beginningOfDocument.

swift
1import UIKit
2
3func cursorOffset(in textField: UITextField) -> Int? {
4    guard let range = textField.selectedTextRange else { return nil }
5    return textField.offset(from: textField.beginningOfDocument, to: range.start)
6}
7
8func cursorOffset(in textView: UITextView) -> Int? {
9    guard let range = textView.selectedTextRange else { return nil }
10    return textView.offset(from: textView.beginningOfDocument, to: range.start)
11}

This works for both controls because they both conform to UITextInput.

If the selection is collapsed, that offset is the caret position. If the user has highlighted text, it is the start of the selection range.

Set the Cursor Position

To place the cursor at a specific offset, create a UITextPosition from the document start and assign a collapsed selectedTextRange.

swift
1func setCursor(_ offset: Int, in textField: UITextField) {
2    guard let position = textField.position(from: textField.beginningOfDocument, offset: offset),
3          let range = textField.textRange(from: position, to: position) else {
4        return
5    }
6    textField.selectedTextRange = range
7}
8
9func setCursor(_ offset: Int, in textView: UITextView) {
10    guard let position = textView.position(from: textView.beginningOfDocument, offset: offset),
11          let range = textView.textRange(from: position, to: position) else {
12        return
13    }
14    textView.selectedTextRange = range
15}

Always guard the offset because text may be shorter than expected after editing or formatting.

Preserve the Caret While Reformatting Text

A common use case is text formatting, such as adding spaces to a credit-card input. Without explicit cursor management, the caret usually jumps to the end.

swift
1func formatCardNumber(in textField: UITextField) {
2    let oldOffset = cursorOffset(in: textField) ?? 0
3    let raw = (textField.text ?? "").replacingOccurrences(of: " ", with: "")
4
5    let chunks = stride(from: 0, to: raw.count, by: 4).map { index -> String in
6        let start = raw.index(raw.startIndex, offsetBy: index)
7        let end = raw.index(start, offsetBy: 4, limitedBy: raw.endIndex) ?? raw.endIndex
8        return String(raw[start..<end])
9    }
10
11    let formatted = chunks.joined(separator: " ")
12    textField.text = formatted
13    setCursor(min(oldOffset, formatted.count), in: textField)
14}

That pattern makes typing feel stable instead of erratic.

Remember That Selection Is Not Always a Caret

selectedTextRange may represent a selection rather than a single insertion point. If your code assumes a collapsed caret, verify that start and end are the same.

This matters for mention insertion, autocomplete, or replace operations. Overwriting the user’s selection when you thought you were moving only the caret produces frustrating editing bugs.

Main-Thread and Timing Considerations

Text selection updates belong on the main thread. In some delegate callbacks, setting the selection immediately can conflict with UIKit’s own text updates. If that happens, scheduling the cursor change on the next run loop can help.

swift
DispatchQueue.main.async {
    setCursor(3, in: textField)
}

Use that only when necessary. It is a workaround for timing, not the default pattern.

Common Pitfalls

  • Treating cursor position as a plain integer property instead of using UITextPosition and UITextRange.
  • Setting a caret offset that is outside the current text bounds.
  • Forgetting that selectedTextRange may represent a selection rather than a collapsed caret.
  • Updating selection before the text change has finished, which causes visible cursor jumps.

Summary

  • Read caret position using selectedTextRange and beginningOfDocument.
  • Set caret position by creating a UITextPosition and collapsed UITextRange.
  • The same pattern works for both UITextField and UITextView.
  • Preserve cursor position explicitly when reformatting text.
  • Validate offsets and keep selection updates on the main thread.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.