iOS
UITextField
Swift
Objective-C
text input

Using textFieldshouldChangeCharactersInRange, how do I get the text including the current typed character?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In textField(_:shouldChangeCharactersIn:replacementString:), the text field has not yet applied the user edit. That means textField.text still holds the previous value, not the candidate value after typing, delete, or paste. To validate or format correctly, you must compute the updated string manually from current text, edit range, and replacement text.

Why textField.text Is Stale in This Callback

The delegate method runs before UI update, giving you a chance to allow or reject the proposed change.

You receive:

  • current text
  • 'NSRange of characters to replace'
  • replacement string

Use these three values to build the candidate result.

Swift Pattern for Computing Updated Text

Convert NSRange into Swift Range and replace characters safely.

swift
1func textField(_ textField: UITextField,
2               shouldChangeCharactersIn range: NSRange,
3               replacementString string: String) -> Bool {
4    let current = textField.text ?? ""
5
6    guard let swiftRange = Range(range, in: current) else {
7        return false
8    }
9
10    let updated = current.replacingCharacters(in: swiftRange, with: string)
11    print("updated:", updated)
12
13    return true
14}

This handles typing, delete, and paste with one logic path.

Use Updated Text for Validation

Max-length checks and content rules should use updated, not current.

swift
1func textField(_ textField: UITextField,
2               shouldChangeCharactersIn range: NSRange,
3               replacementString string: String) -> Bool {
4    let current = textField.text ?? ""
5    guard let swiftRange = Range(range, in: current) else { return false }
6
7    let updated = current.replacingCharacters(in: swiftRange, with: string)
8
9    let maxLength = 20
10    if updated.count > maxLength {
11        return false
12    }
13
14    return true
15}

This avoids common off-by-one issues in input length restrictions.

Apply Live Formatting Correctly

If you format text manually, assign formatted value and return false to prevent double edits.

swift
1func formatCardDigits(_ raw: String) -> String {
2    let digits = raw.filter { $0.isNumber }
3    var result = ""
4
5    for (idx, ch) in digits.enumerated() {
6        if idx > 0 && idx % 4 == 0 { result.append(" ") }
7        result.append(ch)
8    }
9
10    return result
11}
12
13func textField(_ textField: UITextField,
14               shouldChangeCharactersIn range: NSRange,
15               replacementString string: String) -> Bool {
16    let current = textField.text ?? ""
17    guard let swiftRange = Range(range, in: current) else { return false }
18
19    let updated = current.replacingCharacters(in: swiftRange, with: string)
20    textField.text = formatCardDigits(updated)
21    return false
22}

Returning true after manual assignment can duplicate character operations.

Handle IME and Marked Text

For languages using composition input methods, validation can run too early if you ignore marked text state.

swift
if textField.markedTextRange != nil {
    return true
}

Apply strict validation after composition commits to avoid breaking non-Latin input behavior.

Objective-C Equivalent

The same computation pattern applies in Objective-C.

objective-c
1- (BOOL)textField:(UITextField *)textField
2shouldChangeCharactersInRange:(NSRange)range
3replacementString:(NSString *)string {
4    NSString *current = textField.text ?: @"";
5    NSString *updated = [current stringByReplacingCharactersInRange:range withString:string];
6    NSLog(@"%@", updated);
7    return YES;
8}

If app has mixed Swift and Objective-C code, keep update logic consistent across both implementations.

Reuse Helper Logic Across Text Fields

When several text fields share similar behavior, extract candidate-text computation into a helper.

swift
1func applyingEdit(current: String, range: NSRange, replacement: String) -> String? {
2    guard let swiftRange = Range(range, in: current) else { return nil }
3    return current.replacingCharacters(in: swiftRange, with: replacement)
4}

Centralized logic reduces duplicated bugs and simplifies test coverage.

Testing Input Edge Cases

Include tests for:

  • insert in middle
  • full selection replacement
  • delete from end
  • emoji and composed characters
  • long paste content

Many text bugs appear only in these non-trivial edit paths.

Common Pitfalls

A common pitfall is reading textField.text and assuming it already includes the typed character.

Another pitfall is applying validation to old text instead of computed candidate text.

A third pitfall is returning true after manually assigning textField.text, causing duplicated edits.

Teams also ignore marked text behavior and accidentally break input for composition-based keyboards.

Summary

  • Delegate callback fires before the text field applies user edits.
  • Compute candidate text manually from current text, range, and replacement.
  • Use candidate text for validation and formatting decisions.
  • Return false when you assign formatted text manually.
  • Handle marked text and edge cases to keep input behavior correct for all users.

Course illustration
Course illustration

All Rights Reserved.