iOS 13 Crash with SwipeKeyboard and textfield:shouldChangeCharactersIn:

Viewed 1211

In iOS 13, when implementing shouldChangeCharactersIn via the UITextfieldDelegate, the application crashes when using the swiping keyboard.

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if let text = textField.text as NSString? {
            let txtAfterUpdate = text.replacingCharacters(in: range, with: string)
            textField.text = txtAfterUpdate
        }
        return false
    }

Is this an Apple bug?

3 Answers

I was able to reproduce this - if you mutate the state of the text on a UITextField during swipe entry - and only during swipe entry, it'll attempt to reinsert the swiped content (even if you return false), which retriggers your delegate event, which kicks off the recursive cycle.

It's a bit of a hack but you could catch it with something like

    private var lastEntry: String?

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if string.count > 1 && string == lastEntry { // implies we're swiping or pasting
            print("Caught unwanted recursion")
            return
        }
        lastEntry = string
        if let text = textField.text as NSString? {
            let txtAfterUpdate = text.replacingCharacters(in: range, with: string)
            textField.text = txtAfterUpdate
        }
        return false
    }

It'll stop users from pasting/swiping the same thing twice in a row, but at least it'll let them swipe while Apple fixes their problem.

I used UIPasteboard to identify when the user is pasting and then leave the text as the user entered using the swipe like this:

public func textField(_ textField: UITextField,
                      shouldChangeCharactersIn range: NSRange,
                      replacementString string: String) -> Bool {

    //check if the user used swipe keyboard
    if string.count > 1 && string != UIPasteboard.general.string ?? "" {
        return true
    }
    //do the text treatment
    return false
}

I also realized that the TextField only accepts static strings when using swipe keyboard. Hope it Helps.

Before setting text you can reset delegate and after set it to self again. But this solution has one problem if textfield is empty - text will be doubled.

Му code example:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let currentText: String = textField.text ?? ""
    if #available(iOS 13, *) {
        textField.delegate = nil
        let resultText = editedText
        textField.text = resultText
        if currentText.isEmpty, textField.text != resultText {
            textField.text = resultText
        }
        textField.delegate = self
    } else {
        textField.text = input.result
    }
    return false
}
Related