Checklists: TextFieldShouldClear doneBarButtonEnabled Not Working

So the code below is to disable the Done Button when the clear button is pressed. It works the first time, but if you type and clear again the second and third time hitting clear, the Done Button is enabled when the text field is blank. So you can submit a blank checklist item. Seems that TextFieldShouldClear isn’t called all the time then?

 func textFieldShouldClear (_ textField: UITextField) -> Bool {
        doneBarButton.isEnabled = false
        return true
        
    }

Since you don’t indicate the chapter, page number, or provide a project so that we can see what is going on, it’s difficult to know what is happening :slight_smile: I realize that from your end that everything looks very clear, but from this end, since there are so many possibilities, it’s hard to help someone when enough information is not provided and only a code snippet is.

If this is in Chapter 12, then I have tested the final code for that chapter as provided with the book and it works. You might want to compare your code with the final chapter code or perhaps retrace your steps to see what you might have missed?

Oh, I guess I assumed that the Checklist App is the only one that used this code. To my knowledge, this is the only code associated with disabling the Done Button when the clear button is pressed. I will check back in the book for location.

At first when I tried this I thought “it’s working fine, what’s the problem?” But then after hitting clear, I started typing “Test”, but paused after “Tes”. I hit clear again, and the Done button stayed enabled.

What? Looking closer, I realized that there was an auto-correct about to happen - it wanted to replace “Tes” with “Yes”. After tracing it a bit, I figured out that after clicking the clear X, it was going like this:

  1. calls textFieldShouldClear, to see if it should clear, which sets the Done button disabled, and returns true.
  2. before clearing, it finishes the auto-correct, which changes the text to “Yes”.
  3. when doing that, it calls shouldChangeCharactersIn, which checks that the new text is not blank, and sets the Done button enabled again!
  4. then it actually clears the text.

So that is a snag, a conflict between using the delegate to control the Done button, and having auto-correct enabled.

You could block saving blank names, by putting a check in the done function:

 if let newText = textField.text, !newText.isEmpty {
   //  put save code in here, using newText
 }

You could turn off the Clear option, or turn off the auto-correct. If you want both of those, though, then the best way to control the Done button seems to be using a notification, rather than the delegate.

Put this in the viewDidLoad:

NotificationCenter.default.addObserver(
  self, selector: #selector(setDoneButton(notification:)),
  name: NSNotification.Name.UITextFieldTextDidChange, object: textField)

Then add this func somewhere:

  @objc func setDoneButton(notification: NSNotification) {
      doneBarButton.isEnabled = !textField.text!.isEmpty
  }
2 Likes

Hi @sgerrard, thank you for sharing your solution. I understood your very clear explanation as to why we would be getting this behaviour on the done button button since the field clearing and the auto-correct are at odds with each other at times.

At this point in my learning path, I certainly do not understand the solution you provided, as I have yet to learn about these notifications you make use of.

However, I’m more curious at this point in knowing how you came to understand the problem - and the series of events taking place. Was it just an observation, or did you use some debugging tools to follow the logic?

I used the debugger in Xcode. After seeing the same odd behavior you wrote about, and reading this line of your post:

I first put a breakpoint in TextFieldShouldClear, on this line:

    doneBarButton.isEnabled = false

It hit that line every time I hit Clear, even when the final result was the Done button still being enabled.

So then I found the other place where doneBarButton.isEnabled gets changed, in the method

func textField(_ textField: UITextField, shouldChangeCharactersIn range...

I put a breakpoint on that code as well. Sure enough, the second time I tapped Clear, first textFieldShouldClear got called, making isEnabled = false, then shouldChangeCharactersIn got called, with the autoCorrect value as the text, and isEnabled got set to true.

Then I just played around with making entries with or without an autocorrect, and tapping Clear, until I could say what sequence would leave the Done button enabled after tapping Clear.


It’s a pretty subtle bug. I first did the Checklist app in October 2017, and I never noticed it, nor did anyone else! It occurs because shouldChangeCharactersIn is called before the change, since you can return False to tell it not to make the change. Technically speaking, the Done button enable/disable should happen after the change, not before.

Since the UITextFieldDelegate doesn’t have a call after the change, I hunted around and found the notification called UITextFieldTextDidChange (Did = after). Notifications are a bit more advanced, but I think there is at least one later on in the Apprentice (p 707). They are an older part of iOS, and very complete, but a bit more cumbersome to use.

1 Like

Hi @sgerrard, thank you kindly for taking the time to respond so thoroughly with how you determined what was going on. This gets me thinking I need to get more acquainted with xCode’s debugger! :grinning:

It really is a subtle bug as you pointed out, so I’m not surprised that not too many people seem to have noticed. It’s great to at least know why it was happening now because on the surface it seemed almost random to me. I’m looking forward to getting through the rest of the Apprentice series and exploring these more advanced solutions you mention.

Thanks again!

I had this same bug. Thank you so much for this really great explanation. I put a break point on it, but didn’t really follow it well enough. I understand much better now.

Just for the record, there is another way to do it, if you are using a storyboard. Instead of the notification business, you can hook up the “Editing Changed” event on the textField.

The IBAction in the code will look like this:

  @IBAction func textFieldEditChanged(_ sender: Any) {
    doneBarButton.isEnabled = !textField.text!.isEmpty
  }

and the storyboard hookup looks like this:
image

and that’s all you need!

2 Likes

This topic was automatically closed after 166 days. New replies are no longer allowed.