1
votes

I have a master-detail kind of application OS X 10.9 only:
An NSTableView on the left side of the window, the details as NSTextFields on the right side.
I have not set the tab order specifically, out of the box works just fine.

Except after inserting a new object:

  • after inserting a new object the object is selected in the NSTableView.
  • hit tab
  • the first NSTextField is selected
  • write something, hit tab
  • now instead of selecting the next NSTextField nothing is selected,
  • hit tab
  • the NSTableView is selected
  • hit tab
  • the first NSTextField is selected

What i want:

  • after inserting a new object, the first NSTextField of the detail part should be selected
  • hit tab
  • the next NSTextField of the detail part should be selected

UPDATE: I found that the strange behaviour is triggered by enabling NSArrayController’s "Auto Rearrange Content“ in Interface Builder. If i disable it, i do not loose my focus anymore. See CoreData-bound NSTableView loses input focus when items change, but only if sorted.

BUT, of course, i still want my content to be automatically sortet — just without loosing my focus during that operation. Can this be done?

UPDATE2: i followed @KenThomases advice and recorded the stack trace at the moment, when my textfield looses keyboard focus:

2014-08-17 16:30:26.003 ResponderExperiment[712:303] (
    0   ResponderExperiment                 0x00000001000014dd -[WMWindow makeFirstResponder:] + 61
    1   AppKit                              0x00007fff8d25c778 -[NSTextView(NSPrivate) _giveUpFirstResponder:] + 257
    2   AppKit                              0x00007fff8d25c56c -[NSTextView(NSKeyBindingCommands) insertTab:] + 270
    3   AppKit                              0x00007fff8d22dc2f -[NSResponder doCommandBySelector:] + 71
    4   AppKit                              0x00007fff8d25b10a -[NSTextView doCommandBySelector:] + 196
    5   AppKit                              0x00007fff8d22d151 -[NSKeyBindingManager(NSKeyBindingManager_MultiClients) interpretEventAsCommand:forClient:] + 1392
    6   AppKit                              0x00007fff8d24c1c2 -[NSTextInputContext handleEvent:] + 845
    7   AppKit                              0x00007fff8d22b9dd -[NSView interpretKeyEvents:] + 180
    8   AppKit                              0x00007fff8d24bd6d -[NSTextView keyDown:] + 658
    9   AppKit                              0x00007fff8d1f856b -[NSWindow sendEvent:] + 1843
    10  AppKit                              0x00007fff8d199b32 -[NSApplication sendEvent:] + 3395
    11  AppKit                              0x00007fff8cfe99f9 -[NSApplication run] + 646
    12  AppKit                              0x00007fff8cfd4783 NSApplicationMain + 940
    13  ResponderExperiment                 0x0000000100002de2 main + 34
    14  libdyld.dylib                       0x00007fff8c5bf5fd start + 1
    15  ???                                 0x0000000000000003 0x0 + 3
)
2014-08-17 16:30:26.014 ResponderExperiment[712:303] (
    0   ResponderExperiment                 0x00000001000014dd -[WMWindow makeFirstResponder:] + 61
    1   AppKit                              0x00007fff8d03c918 -[NSControl abortEditing] + 83
    2   AppKit                              0x00007fff8d2723e0 -[NSValueBinder discardEditing] + 162
    3   AppKit                              0x00007fff8d1aee30 -[NSController discardEditing] + 115
    4   AppKit                              0x00007fff8d1af971 -[NSArrayController rearrangeObjects] + 27
    5   AppKit                              0x00007fff8d3697a9 -[NSArrayController observeValueForKeyPath:ofObject:change:context:] + 294
    6   AppKit                              0x00007fff8d39ac08 -[NSArrayController _setMultipleValue:forKeyPath:atIndex:] + 323
    7   AppKit                              0x00007fff8d39ba12 -[NSArrayController _setSingleValue:forKeyPath:] + 137
    8   Foundation                          0x00007fff8bf9e19a -[NSObject(NSKeyValueCoding) setValue:forKeyPath:] + 285
    9   AppKit                              0x00007fff8d25444c -[NSBinder _setValue:forKeyPath:ofObject:mode:validateImmediately:raisesForNotApplicableKeys:error:] + 364
    10  AppKit                              0x00007fff8d254287 -[NSBinder setValue:forBinding:error:] + 245
    11  AppKit                              0x00007fff8d74c4ee -[NSValueBinder _applyObjectValue:forBinding:canRecoverFromErrors:handleErrors:typeOfAlert:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:] + 194
    12  AppKit                              0x00007fff8d74c871 -[NSValueBinder applyDisplayedValueHandleErrors:typeOfAlert:canRecoverFromErrors:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:error:] + 621
    13  AppKit                              0x00007fff8d74c9d4 -[NSValueBinder _applyDisplayedValueIfHasUncommittedChangesWithHandleErrors:typeOfAlert:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:error:] + 127
    14  AppKit                              0x00007fff8d253c2e -[NSValueBinder validateAndCommitValueInEditor:editingIsEnding:errorUserInterfaceHandled:] + 436
    15  AppKit                              0x00007fff8d253a57 -[_NSBindingAdaptor _validateAndCommitValueInEditor:editingIsEnding:errorUserInterfaceHandled:bindingAdaptor:] + 160
    16  AppKit                              0x00007fff8d25399d -[_NSBindingAdaptor validateAndCommitValueInEditor:editingIsEnding:errorUserInterfaceHandled:] + 260
    17  AppKit                              0x00007fff8d25e0ba -[NSTextField textShouldEndEditing:] + 402
    18  AppKit                              0x00007fff8d25dc63 -[NSTextView(NSSharing) resignFirstResponder] + 393
    19  AppKit                              0x00007fff8d136170 -[NSWindow makeFirstResponder:] + 455
    20  ResponderExperiment                 0x000000010000154c -[WMWindow makeFirstResponder:] + 172
    21  AppKit                              0x00007fff8d25c778 -[NSTextView(NSPrivate) _giveUpFirstResponder:] + 257
    22  AppKit                              0x00007fff8d25c56c -[NSTextView(NSKeyBindingCommands) insertTab:] + 270
    23  AppKit                              0x00007fff8d22dc2f -[NSResponder doCommandBySelector:] + 71
    24  AppKit                              0x00007fff8d25b10a -[NSTextView doCommandBySelector:] + 196
    25  AppKit                              0x00007fff8d22d151 -[NSKeyBindingManager(NSKeyBindingManager_MultiClients) interpretEventAsCommand:forClient:] + 1392
    26  AppKit                              0x00007fff8d24c1c2 -[NSTextInputContext handleEvent:] + 845
    27  AppKit                              0x00007fff8d22b9dd -[NSView interpretKeyEvents:] + 180
    28  AppKit                              0x00007fff8d24bd6d -[NSTextView keyDown:] + 658
    29  AppKit                              0x00007fff8d1f856b -[NSWindow sendEvent:] + 1843
    30  AppKit                              0x00007fff8d199b32 -[NSApplication sendEvent:] + 3395
    31  AppKit                              0x00007fff8cfe99f9 -[NSApplication run] + 646
    32  AppKit                              0x00007fff8cfd4783 NSApplicationMain + 940
    33  ResponderExperiment                 0x0000000100002de2 main + 34
    34  libdyld.dylib                       0x00007fff8c5bf5fd start + 1
    35  ???                                 0x0000000000000003 0x0 + 3
)

So perhaps I could write my own NSArrayController rearrangeObjects: method, that does not loose focus?

2
I would try experimenting with the control text delegate methods (e.g. -controlTextDidEndEditing:) and the first responder methods (e.g. -resignFirstResponder). The hope is to detect when this is about to happen, set a flag, and then refuse to allow it to happen. Or, possibly, restore first responder to the desired view after the array controller messes it up. You might also investigate exactly where in NSArrayController the focus is being changed (for example, by logging [NSThread callStackSymbols] from an override of -[NSWindow makeFirstResponder:]) and see if you can modify that.Ken Thomases
@KenThomases I did as you suggested. I added the relevant stacktrace to the question.MartinW

2 Answers

2
votes

Here's a possible solution. The idea is to re-implement the response of the text field (or, more accurately, the field editor serving the text field) to the Tab key as two separate steps. First, end editing, which will make the array controller rearrange its content. We end editing by resetting focus, so we don't care what the array controller may happen to do to focus anyway. Then, advance the focus from the originally-focused text field ourselves.

Set a delegate on your text fields. In that delegate, implement the following method:

- (BOOL) control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)command
{
    if (command == @selector(insertTab:))
    {
        // End editing. This will commit the edited value and cause the array controller
        // to rearranged its contents.
        [control.window makeFirstResponder:nil];

        // This will put focus on the next control from the text field.
        [control.window selectKeyViewFollowingView:control];

        // Prevent the normal processing of this command
        return YES;
    }

    if (command == @selector(insertBacktab:))
    {
        // End editing. This will commit the edited value and cause the array controller
        // to rearranged its contents.
        [control.window makeFirstResponder:nil];

        // This will put focus on the previous control from the text field.
        [control.window selectKeyViewPrecedingView:control];

        // Prevent the normal processing of this command
        return YES;
    }

    return NO;
}

One thing I'm not sure of: what happens if instead of Tab-ing away from the text field, you click on another text field. Does the array controller still reset focus in that case?

0
votes

In my case, I have overrided abortEditing method of NSControl, with my own logic.

- (BOOL)abortEditing {
    MyObject *object = [myArrayController.arrangedObjects objectAtIndex:self.editedRow];
    return ![[Manager sharedInstance] isObjectBeingEditing:object];
}