Fix: UITextField Text Change Event Reads Old Text in Swift

Fix: UITextField Text Change Event Reads Old Text in Swift

Written by

in

Quick answer: Use the text field’s .editingChanged control event when you need to read its value after the user edits it. Connect that event to a target-action method, then read textField.text and update any dependent fields there.

Why this happens

textField(_:shouldChangeCharactersIn:replacementString:) is a delegate method for deciding whether an edit should be accepted. UIKit calls it before applying the proposed change. At that moment, textField.text still contains the previous value, so a calculation inside the method can use stale text even if you return true.

For example, if a user types 5 into an empty field, the delegate can receive the proposed character while the field’s text property is still empty. Returning true allows UIKit to apply the edit, but it does not make the text update happen before the rest of that delegate method runs.

Use the UIControl.Event.editingChanged event for work that needs the field’s updated value. It is sent when the user changes the text during editing, so your handler can read the value after UIKit has applied the change.

How to fix it

1. Handle .editingChanged with target-action

Register a target for the text field, usually in viewDidLoad. Use a selector that accepts the text field as its sender:

import UIKit

final class CheckoutViewController: UIViewController {
    @IBOutlet private weak var subtotalField: UITextField!
    @IBOutlet private weak var totalField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()

        subtotalField.addTarget(
            self,
            action: #selector(textFieldDidChange(_:)),
            for: .editingChanged
        )
    }

    @objc private func textFieldDidChange(_ textField: UITextField) {
        let subtotal = Double(textField.text ?? "") ?? 0
        totalField.text = String(subtotal)
    }
}

Replace the example calculation with your own calculateAndUpdateTextFields() logic. If that method already reads the text field’s text property, call it from textFieldDidChange(_:) instead of from shouldChangeCharactersIn.

The sender parameter is useful when one handler serves multiple fields. You can inspect the sender or compare it with your outlets to decide which values to recalculate:

@objc private func textFieldDidChange(_ textField: UITextField) {
    switch textField {
    case subtotalField, shippingField:
        calculateAndUpdateTextFields()
    default:
        break
    }
}

Make sure each field that should trigger the calculation has its own .editingChanged target registration. A target added to one text field does not automatically observe other fields.

2. Keep the delegate for accepting or rejecting edits

You can keep shouldChangeCharactersIn if you need to validate an edit before UIKit applies it. Return true to allow the proposed edit or false to reject it. Move calculations that need the final field value to the .editingChanged handler.

func textField(
    _ textField: UITextField,
    shouldChangeCharactersIn range: NSRange,
    replacementString string: String
) -> Bool {
    // Check whether this proposed edit is allowed.
    return true
}

This gives each callback a clear job: the delegate checks the proposed change, and the control event responds to the updated text. Avoid calling a calculation that reads textField.text inside shouldChangeCharactersIn when it needs to include the character currently being entered.

If you must calculate the proposed value inside the delegate—for example, to reject input that would exceed a limit—build that value from the existing text, the range, and the replacement string. Do not assume the field has already changed. For ordinary updates to labels or related fields, .editingChanged is simpler and less error-prone.

3. Connect the event in Interface Builder

You can make the same connection without calling addTarget in code:

  1. Open the storyboard or XIB containing the text field.
  2. Control-click the text field to open its connections.
  3. Drag the Editing Changed event to the view controller’s action method.
  4. Choose or create an action that accepts the text field sender.

The action runs after a user edit, so read the current text from the sender in that method. If you also register the event in code, remove one of the connections to avoid running the calculation twice.

4. Account for programmatic text changes

.editingChanged is intended for edits made while the user is editing the control. Setting textField.text in code does not, by itself, guarantee that the control event will fire. If your app assigns a value programmatically and needs the same calculation, call the calculation method directly after the assignment:

subtotalField.text = "25.00"
calculateAndUpdateTextFields()

You can also explicitly ask the control to send its editing-changed actions with sendActions(for: .editingChanged), but calling your update method directly often makes the programmatic flow easier to follow. Choose one approach for each assignment so the update does not happen twice.

5. Use an Objective-C action if the screen is written in Objective-C

The UIKit target-action pattern is the same in Objective-C:

[textField addTarget:self
              action:@selector(textFieldDidChange:)
    forControlEvents:UIControlEventEditingChanged];

Then read the current value in the action:

- (void)textFieldDidChange:(UITextField *)textField {
    [self calculateAndUpdateTextFields];
}

How to prevent it

  • Use shouldChangeCharactersIn to accept or reject an edit, not to read the post-edit value.
  • Use .editingChanged for calculations that need the updated text.
  • Register the event on every field that should trigger an update.
  • For programmatic text assignments, call the update logic explicitly or deliberately send the control event.
  • Avoid connecting the same action both in Interface Builder and in code unless duplicate calls are intended.

FAQ

Does .editingChanged run before or after the text changes?

It is the event to use when you need to respond to a user’s editing change and read the updated text. In contrast, shouldChangeCharactersIn is called before UIKit applies the proposed edit.

Will .editingChanged fire when I set textField.text in code?

Not automatically just because you assign the text property. Call your update method after the assignment, or explicitly send .editingChanged actions if that fits your design.

Should I remove UITextFieldDelegate?

No. Keep the delegate if you need to validate, format, or reject proposed edits. Use target-action for work that needs the field’s value after the user’s edit has been applied.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 3.0; this article is shared under the same license.