Swift
shouldChangeCharactersInRange
iOS development
UITextField
Swift programming

How shouldChangeCharactersInRange works 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

shouldChangeCharactersIn range: is a UITextFieldDelegate method that is called every time the user types, deletes, or pastes text into a UITextField. It gives you the opportunity to inspect the proposed change and either allow or reject it by returning true or false. This is the primary mechanism for input validation, character limiting, and text formatting in iOS text fields.

Method Signature

swift
1func textField(
2    _ textField: UITextField,
3    shouldChangeCharactersIn range: NSRange,
4    replacementString string: String
5) -> Bool

Parameters

  • textField: The text field that is being edited.
  • range: An NSRange indicating which characters in the current text will be replaced. For a simple insertion, range.length is 0. For a deletion, string is empty.
  • string: The replacement string. This is the text being typed or pasted. For a backspace, this is an empty string "".

Return Value

  • true: Allow the replacement to occur.
  • false: Reject the change — the text field remains unchanged.

Understanding the Parameters

swift
1// Example: Text field contains "Hello" and user types "X" after "Hel"
2// textField.text = "Hello"
3// range = NSRange(location: 3, length: 0)  — insert at position 3
4// string = "X"
5// Result if allowed: "HelXlo"
6
7// Example: User selects "ell" and types "a"
8// range = NSRange(location: 1, length: 3)  — replace 3 characters starting at position 1
9// string = "a"
10// Result if allowed: "Hao"
11
12// Example: User presses backspace with cursor after "o"
13// range = NSRange(location: 4, length: 1)  — delete 1 character at position 4
14// string = ""
15// Result if allowed: "Hell"

Computing the Resulting Text

To see what the text would look like after the change:

swift
1func textField(_ textField: UITextField,
2               shouldChangeCharactersIn range: NSRange,
3               replacementString string: String) -> Bool {
4
5    let currentText = textField.text ?? ""
6    guard let textRange = Range(range, in: currentText) else { return false }
7    let updatedText = currentText.replacingCharacters(in: textRange, with: string)
8
9    print("Text will become: \(updatedText)")
10    return true
11}

Common Use Cases

Character Limit

Restrict the text field to a maximum number of characters:

swift
1func textField(_ textField: UITextField,
2               shouldChangeCharactersIn range: NSRange,
3               replacementString string: String) -> Bool {
4
5    let currentText = textField.text ?? ""
6    guard let textRange = Range(range, in: currentText) else { return false }
7    let updatedText = currentText.replacingCharacters(in: textRange, with: string)
8
9    return updatedText.count <= 50  // Max 50 characters
10}

Numbers Only

Allow only numeric input:

swift
1func textField(_ textField: UITextField,
2               shouldChangeCharactersIn range: NSRange,
3               replacementString string: String) -> Bool {
4
5    // Allow backspace
6    if string.isEmpty { return true }
7
8    // Only allow digits
9    return string.allSatisfy { $0.isNumber }
10}

Phone Number Formatting

Auto-format as the user types:

swift
1func textField(_ textField: UITextField,
2               shouldChangeCharactersIn range: NSRange,
3               replacementString string: String) -> Bool {
4
5    let currentText = textField.text ?? ""
6    guard let textRange = Range(range, in: currentText) else { return false }
7    let updatedText = currentText.replacingCharacters(in: textRange, with: string)
8
9    // Strip non-digits
10    let digits = updatedText.filter { $0.isNumber }
11    guard digits.count <= 10 else { return false }
12
13    // Format: (123) 456-7890
14    var formatted = ""
15    for (index, digit) in digits.enumerated() {
16        if index == 0 { formatted += "(" }
17        if index == 3 { formatted += ") " }
18        if index == 6 { formatted += "-" }
19        formatted.append(digit)
20    }
21
22    textField.text = formatted
23    return false  // We set the text manually
24}

Decimal Input (Max 2 Decimal Places)

swift
1func textField(_ textField: UITextField,
2               shouldChangeCharactersIn range: NSRange,
3               replacementString string: String) -> Bool {
4
5    let currentText = textField.text ?? ""
6    guard let textRange = Range(range, in: currentText) else { return false }
7    let updatedText = currentText.replacingCharacters(in: textRange, with: string)
8
9    // Allow empty (clearing the field)
10    if updatedText.isEmpty { return true }
11
12    // Validate decimal format
13    let components = updatedText.split(separator: ".", omittingEmptySubsequences: false)
14    if components.count > 2 { return false }  // Multiple decimal points
15    if components.count == 2 && components[1].count > 2 { return false }  // More than 2 decimal places
16
17    return Double(updatedText) != nil || updatedText == "."
18}

Disabling Paste

swift
1func textField(_ textField: UITextField,
2               shouldChangeCharactersIn range: NSRange,
3               replacementString string: String) -> Bool {
4
5    // Pasted text is typically longer than 1 character
6    if string.count > 1 {
7        return false  // Block paste
8    }
9    return true
10}

NSRange vs Swift Range

A critical detail: the range parameter is an NSRange, which works with UTF-16 code units (Objective-C NSString). Swift strings use Unicode scalars. For strings with emoji or non-ASCII characters, you must convert properly:

swift
1// CORRECT: Convert NSRange to Swift Range
2guard let textRange = Range(range, in: currentText) else { return false }
3let updatedText = currentText.replacingCharacters(in: textRange, with: string)
4
5// WRONG: Using NSRange directly with Swift string subscripts
6// This can crash with emoji or multi-byte characters

Common Pitfalls

  • Returning false but not updating text: If you return false to apply custom formatting (like the phone number example), you must set textField.text yourself. Returning false without setting the text means no change occurs.
  • NSRange/Swift Range mismatch: Always use Range(range, in: currentText) to convert. Direct NSRange subscripting on Swift strings causes crashes with emoji, accented characters, or CJK text.
  • Not handling paste: When the user pastes, string contains the entire pasted content. If you only validate single characters, pasted text may bypass your validation.
  • Cursor position after manual text update: When you set textField.text manually and return false, the cursor jumps to the end. To maintain cursor position, use textField.selectedTextRange.
  • Combine with editingChanged: For real-time validation UI (enabling/disabling a button), also listen to the .editingChanged control event, as shouldChangeCharactersIn fires before the change happens.

Summary

  • shouldChangeCharactersIn range: is called before every text change — return true to allow, false to reject
  • Use Range(range, in: currentText) to safely convert NSRange to Swift Range
  • Compute the resulting text with replacingCharacters(in:with:) to make validation decisions
  • For custom formatting, set textField.text manually and return false
  • Always handle empty string (backspace) and multi-character string (paste)

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.