UITextField stuck in an infinite loop on swipe input

In order to format user input in UITextField, we define a custom textField(_:shouldChangeCharactersIn:replacementString:) method. Below you can see code similar to the one we use in production.

func textField(
    _ textField: UITextField,
    shouldChangeCharactersIn range: NSRange,
    replacementString string: String
) -> Bool {
    let result = replaceCharacters(
        inText: textField.text ?? "",
        range: range,
        withCharacters: string
    )

    let formattedResult = format(result)
    textField.text = formattedResult

    return false
}

While normal input works fine, when our users input text with a continuous swipe this code sends UITextField into an infinite loop. You can see the resulting stacktrace below.

What are we doing wrong here?

Here's a sample ViewController demonstrating the issue:

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    private let textField = UITextField()

    override func viewDidLoad() {
        super.viewDidLoad()

        textField.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(textField)

        NSLayoutConstraint.activate([
            textField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            textField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            textField.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])

        textField.delegate = self

        textField.placeholder = "Add text here"
        textField.borderStyle = .roundedRect
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        textField.becomeFirstResponder()
    }

    func textField(
        _ textField: UITextField,
        shouldChangeCharactersIn range: NSRange,
        replacementString string: String
    ) -> Bool {
        let result = replaceCharacters(
            inText: textField.text ?? "",
            range: range,
            withCharacters: string
        )

        textField.text = result

        return false
    }

    private func replaceCharacters(inText text: String, range: NSRange, withCharacters newText: String) -> String {
        let result = NSMutableString(string: text)

        if range.length > 0 {
            result.replaceCharacters(in: range, with: newText)
        } else {
            result.insert(newText, at: range.location)
        }

        return result as String
    }

}

Fwiw, I am seeing the same behavior, even in the simulator. Slow swiping seems to work (most of the time), but faster swiping (like in the way most people would hope to use it) triggers the infinite loop. If I could turn off the swipe behavior for the keyboard that'd be great! But alas, there is no way to do that. I am replying in the hopes that another report spawns at least recognition. Have you filed an "official" bug?

I have faced the same bug. One of the possible workarounds is adding a boolean flag to the delegate in order to avoid the infinite loop.

func textField(
    _ textField: UITextField,
    shouldChangeCharactersIn range: NSRange,
    replacementString string: String
) -> Bool {
    guard !isFormattingInput else { return false }
    let result = replaceCharacters(
        inText: textField.text ?? "",
        range: range,
        withCharacters: string
    )

    let formattedResult = format(result)
    isFormattingInput = true
    textField.text = formattedResult
    isFormattingInput = false

    return false
}
UITextField stuck in an infinite loop on swipe input
 
 
Q