App crashes when performing Undo with three finger tap after Dictation

919 views Asked by At

I got a problem. Given I have a UITextField an a button to delete the content of the text field.

So this works as expected:

  1. Enter text with keyboard
  2. tap on delete button
  3. tap with three fingers and select undo from the context menu
  4. text re-appears

Now things start to get weird when using dictation:

  1. tap the dictation button
  2. say some words so they appear in the text field
  3. tap the delete button
  4. tap with three fingers and select undo from the context menu
  5. App crashes

The crash is Terminating app due to uncaught exception 'NSRangeException', reason: 'NSMutableRLEArray replaceObjectsInRange:withObject:length:: Out of bounds'

So it seems that the text from the dictation does not end up in the list of possible undos. Does anyone havens idea on how to solve this? What I tried so far:

  • Clean app with basic code to verify (see code at the end of this post)
  • Creating a subclass of UITextField to override func dictationRecordingDidEnd() and registering new undo with the text
  • scratched my head and wondering why

I would be very happy to have a solution here!

Code Example for testing app:

import UIKit

class ViewController: UIViewController {

    let textField = UITextField(frame: CGRect(x: 50, y: 50, width: 200, height: 44))

    override func viewDidLoad() {
        super.viewDidLoad()

        textField.layer.borderColor = UIColor.black.cgColor
        textField.layer.borderWidth = 1
        view.addSubview(textField)

        let deletebutton = UIButton(type: .custom)
        deletebutton.frame = CGRect(x: 50, y: 100, width: 100, height: 50)
        deletebutton.addTarget(self, action: #selector(deleteText(_:)), for: .touchUpInside)
        deletebutton.setTitle("delete", for: .normal)
        deletebutton.setTitleColor(.black, for: .normal)
        view.addSubview(deletebutton)
    }

    @objc func deleteText(_ sender: UIButton) {
        textField.text = nil
    }
}

Update 1

I now added the clear button to the text field by setting textField.clearButtonMode = .whileEditing and this works as expected. So the main question is: What does the built in clear button different than a custom one? Is some notification fired? Some other magic? Please enlighten me!

Update 2

The app also crashes when doing this:

  1. dictate something
  2. replace text with different one by using textField.text = "Something"
  3. do a three finger tap and select undo
1

There are 1 answers

1
Tomte On BEST ANSWER

Since there where no answers yet, I will answer it as I found a solution.

I can bot answer why this happens but it seems to be pretty common that this behavior crashes apps which are widely used and available in the App Store.

Anyway, the following code will prevent a crash and is based on the answer listed here :

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        guard let text = textField.text else {
            return true
        }

        let str = text + string
        guard str.count > 0 else {
            return false
        }

        if string.isEmpty && range.length > 0 {
            textField.text = text.count > range.length ? String(text.dropLast(range.length)) : ""
            return false
        }
        return true
    }

I'm happy to help anyone else with this answer!