cappuccino/cappuccino — Issue #1435: CPTextField + Bindings: textDidEndEditing sent too late when controller selection changes
Author: Toxe
Created: 2012-01-19 15:37:56+00:00
State: open

### Application setup

- a CPArrayController that stores a couple of simple objects
- a CPTableView with one column
- the table column values are bound to the arrangedObjects of the array controller
- a CPTextField
- the "value" binding of the text field is bound to a key path of the controller, "selection.translations.en" in this case

If you select an entry from the table the value of the text field changes as well. This is all very basic stuff and nothing fancy as you can see in this [screenshot](http://toxe.net/stuff/capp_textfield_bindings.png).

### The problem

If I enter text into the text field and then select another table entry the textDidEndEditing notification is sent after the selection changed. The consequence is that the newly entered value wont be saved. Let's have a look at some log output:

(1) [info]: textDidFocus
(2) [info]: insertText 1
[info]: textDidBeginEditing
[info]: textDidChange
[info]: insertText 2
[info]: textDidChange
[info]: insertText 3
[info]: textDidChange
(3) [info]: (Controller) setValue:[number of indexes: 1 (in 1 range), indexes: (2)] forKeyPath:selectionIndexes
(4) [info]: setObjectValue:MPEG Video
(5) [info]: textDidEndEditing
(6) [info]: (Controller) setValue:MPEG Video forKeyPath:selection.translations.en
[info]: (Controller) setValue:MPEG Video forKeyPath:selection.translations.en
(7) [info]: textDidBlur

1. I clicked into the text field to start typing.
2. I started typing `1`, `2` and `3` and we see the textDidBeginEditing and textDidChange notifications.
3. I clicked on another table entry and the controller selection changes.
4. The value of the newly selected table entry is written into the text field. This overwrites our `123` text we just entered.
5. Finally the text field receives its textDidEndEditing notification.
6. Now that the text field believes that editing has ended it informes the controller and sends it what the text field believes to be the newly entered text.
7. The text field loses its input focus.

See the problem? The text field sends the wrong value to the controller. It should send `123`, but instead it sent `MPEG Video`, which is the text of the newly selected table element.

The textDidEndEditing and textDidBlur notifications should probably be sent right before or after step (3), where the controller changes its selection.

Note that if I enter the text into the text field and instead of selecting a new table entry I simply press enter or click right outside of the text field everything works fine.

### Workaround

A simple workaround that seems to work fine in this case was that I subclass CPTextField and overwrite `textDidChange:` and after every entered character manually send a textDidEndEditing notification.

- (void)textDidChange:(CPNotification)note
{
CPLog(@"textDidChange");
[super textDidChange:note];

if (_isEditing) {
_isEditing = NO;
[self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]];
}
}

As you can see in the following log output the controller gets notified of the new value immediately. Still the textDidBlur is sent way too late, I suppose.

[info]: textDidFocus
[info]: insertText 1
[info]: textDidBeginEditing
[info]: textDidChange
[info]: textDidEndEditing
[info]: (Controller) setValue:1 forKeyPath:selection.translations.en
[info]: setObjectValue:1
[info]: insertText 2
[info]: textDidBeginEditing
[info]: textDidChange
[info]: textDidEndEditing
[info]: (Controller) setValue:12 forKeyPath:selection.translations.en
[info]: setObjectValue:12
[info]: insertText 3
[info]: textDidBeginEditing
[info]: textDidChange
[info]: textDidEndEditing
[info]: (Controller) setValue:123 forKeyPath:selection.translations.en
[info]: setObjectValue:123
[info]: (Controller) setValue:[number of indexes: 1 (in 1 range), indexes: (1)] forKeyPath:selectionIndexes
[info]: setObjectValue:MPEG-4 Video
[info]: textDidBlur