4

Very simple question that's driving me crazy: What's the proper way to clear the selection of an NSArrayController programmatically?

I'm designing a view with the following components:

  • NSArrayController *controller1: Bound to an array of objects
  • NSPopUpView view1: Content bound to controller1.arrangedObjects; Value bound to controller1.selection; "Inserts Null Placeholder" selected
  • NSArrayController *controller2: Bound to an array stored in controller1.selection
  • NSPopupView view2: Content bound to controller2.arrangedObjects; value bound to controller2.selection; "Inserts Null Placeholder" selected

Initially, view1's content is populated; controller1 and controller2 have nil selection values; and view1 and view2 display null placeholders. Selection of controller1 causes controller1's selection to change and view2's content to populate. All good.

I'd like to implement a Clear button that clears the selection of controller1, which, thanks to bindings, should also clear the selection of controller2 and reset view1 and view2 to the null placeholder. For the life of me, I can't figure out the proper code for this very simple function. Altering the selection of controller1 fails to update the value shown in view1. Worse, altering the controller1 selection programatically causes weird things to happen in controller2: further selection of values in view1 fails to have any effect on view2.

Things I've tried:

  • Calling the SetSelectedObjects method of controller1 with an [NSArray new].

  • Calling the SetSelectedObjects method of controller1 with null.

  • Calling the SetSelectedIndex method of controller1 with NSNotFound.

  • Calling the RemoveSelectedIndex method of controller1 with the SelectedIndex property of controller1.

  • Looking in the Cocoa NSArrayController documentation for any class method or suggestion for clearing the selection value. Nothing there - not even any mention of this being desirable, let alone how to accomplish it.

Any ideas? Thanks...

David Stein
  • 866
  • 7
  • 21

4 Answers4

4

According to Apples Developer documentation this can be done using setSelectionIndexes:

To deselect all indexes, pass an empty index set.

Objective-C:

[arrayController setSelectionIndexes:[NSIndexSet indexSet]];

Swift:

arrayController.setSelectionIndexes( NSIndexSet() )

freytag
  • 4,769
  • 2
  • 27
  • 32
2

Try controller1.selectionIndex = NSIntegerMax; and see if that works. I did a simple test with a label bound to the array controller's selection, and when I set the selectionIndex to NSIntegerMax, the no selection placeholder was displayed in the label.

rdelmar
  • 103,982
  • 12
  • 207
  • 218
  • That's the way Apple set it up, so I don't think it's really that ugly -- if you log selectionIndex when the table has no selection, that's what you get (it actually just prints out a really large number, but I think that's just what NSIntegerMax is). – rdelmar Aug 24 '12 at 17:38
1

As it turns out, the NSArrayController page does include a single recommendation for clearing the selection:

setSelectionIndexes:

Sets the receiver’s selection indexes and returns a Boolean value that indicates whether the selection changed.

(BOOL) setSelectionIndexes: (NSIndexSet *) indexes

Discussion

Attempting to change the selection may cause a commitEditing message which fails, thus denying the selection change.

To select all the receiver’s objects, indexes should be an index set with indexes [0...count -1]. To deselect all indexes, pass an empty index set.

However, I had actually tried that, and it still left me with a problem.

To generalize:

  • Create two classes, A and B, where A contains a property "NSArray *b_list" that contains a list of instances of B's.

  • Create an application with a property "NSArray *a_list". Populate it with some instances of A, and populate the b_list of each A instance with some B instances.

  • Create a window with two array controllers, Controller_A (bound to a_list) and Controller_B (bound to Controller_A.selection.b_list).

  • Create two pop-up buttons in the window, Popup_A (bound to Controller_A.arrangedObjects) and Popup_B (bound to Controller_B.arrangedObjects).

  • Create a "Clear" button with some logic to clear the selection of Controller_A. ("Some logic" is either the method recommended in the Apple documentation, or any other method.)

  • Run the application. Select an entry in Popup_A. Notice that Popup_B populates with the instances of Controller_A.selection.b_list, as it should.

  • Now hit the Clear button.

ERROR: Note that while the content and selection of Popup_A correctly become null, the same doesn't happen in Popup_B: it presents a blank entry with a single selected (blank) item. Note also that the selection properties of Controller_B indicates that there is a selection, but its properties are weird: selectedIndex points to 0 instead of NSNotFound, and selectedIndexes includes a non-empty selection integer range.

This is clearly an error with the binding logic that can definitely cause some exceptions and logical errors. For example, if there's any sort of binding attached to B_controller.selection, clearing A_controller will raise an exception relating to the selection value in B_controller, since it indicates a selection but points to garbage.

My workaround is not to bind anything directly to the B_controller selection. Instead, access B_controller programmatically, and in view of the selection value of A_controller, like this:

// setting some property c to the value of b_controller:
if ((B_controller.selectedIndex == NSNotFound) || A_controller.selectedIndex == NSNotFound))
    c = nil;
else
    c = [B_controller objectAtIndex: [B_controller.selectedIndex]];

I'm also submitting a bug report to Apple with this info.

David Stein
  • 866
  • 7
  • 21
1

Answer to a very old question but still.

I just needed the same thing, and for me this worked:

[arrayController setAvoidsEmptySelection:NO];
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:
    NSMakeRange(NSNotFound, 0)];
[arrayController setSelectionIndexes:indexes];
Elise van Looij
  • 4,162
  • 3
  • 29
  • 52
Trond
  • 211
  • 1
  • 7