NSTableView - Selecting index by typing - xcode

BACKGROUND:
I have a tableview with a list of names. When you click on a name in the list, it displays additional detail information in another section of the window. Everything is connected and is working correctly.
However...
I would like to use Type Select with this table and have run into the following snag:
When I start typing a name (while the table is selected) it correctly highlights the appropriate name in the table BUT the detailed information to the right of the table does not change.
I know the reason is the code for changing the detail information is in an IBAction method which is only called when you click to select a name in the list, and uses the [sender clickedRow] call to get the index of the selected name.
I also suspect that I need to use the [tableView selectedRow] (since it is being selected, but you are not clicking on it) but I am not quite certain where or how to perform this check.
I'm also thinking that since "type select" isn't sending an action message, I won't be able to use [sender selectedRow] but rather will use [tableView selectedRow]...
QUESTION:
How can I tell when the selected row in a tableview has changed via type select?
Thanks!

Implement the tableViewSelectionDidChange: delegate method in your controller, and ensure that your controller is the NSTableView's delegate.
You could update your view with something like:
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification {
[relatedView updateInformationFromRow:[[aNotification object] selectedRow]];
}
From the documentation:
tableViewSelectionDidChange: Informs the delegate that the table
view’s selection has changed.
Within that, you would update your related view. You could use the aNotification's object, which will be a NSTableViewSelectionDidChangeNotification, again from the documentation, is:
Posted after an NSTableView object's selection changes. The
notification object is the table view whose selection changed. This
notification does not contain a userInfo dictionary.

Related

NSPopupButton not updating when using setValue:forKey: against it's CoreData bound field

I'm working on a CoreData / Document based app. In one area of the UI I've setup a view-mode table with various columns. One column has an NSPopupButton in it with the Selected Index binding setup as Table Cell View.objectValue.startupState.
Picking any of the menu items in the popup will correctly update the startupState attribute on the entity with the index of the menu item clicked and the NSPopupButton text updates as well. I've verified the attribute value is in fact updated by saving, closing, and re-opening the document.
In another column I have an NSPopupButton bound similarly to another attribute in the same entity - Table Cell View.objectValue.mode. Depending on the mode selection it will modify the startupState value through a manual implementation of setMode which does this statement in certain cases:
[self setValue:[[NSNumber alloc] initWithInt:1] forKey:#"startupState"];
The issue I'm having is that the NSPopupButton isn't updating to show the menu item text for the selected index. As before, I saved, closed, and re-opened the document after the above code ran and the correct item was selected / text appeared so I know the setValue call updated the attribute.
Any ideas?
As mentioned in the comments, Volker's suggestion addresses the issue. willChangeValueForKey and didChangeValueForKey messages are needed around the setValue:forKey call like this:
[self willChangeValueForKey:#"startupState"];
[self setValue:[[NSNumber alloc] initWithInt:1] forKey:#"startupState"];
[self didChangeValueForKey:#"startupState"];

Can I set a property when created with bindings?

I'm trying to build a To-do list application. I have 2 tablesviews and one textfield. In the first tableview are the different projects, and when you click on one of them the associated todos appear in the second tableview. It's a pretty basic Master-detail I guess.
I set it all up with bindings.
Right now the way you add a task, is you click on an add button and it adds a row with a placeholder text that's editable.
But what I want, is the user to enter the task in the textfield, press add, and then it adds the todo with the name already set.
So basically I have TodoItem Class with a name property, and my question would be, how do I get the content of the nstextfield and assign it to the name property ?
I tried creating an outlet from the Todoitem class to the textfield, but xcode won't let me connect it....
Tell me if you need to see any code, but since I used bindings, there's almost nothing to show. Thanks!
… how do I get the content of the nstextfield and assign it to the name property ?
Translate that directly into Objective-C:
NSString *contentOfTheNSTextField = [myTextField stringValue];
myNewTask.name = contentOfTheNSTextField;
You'd do that in the action method that you've set both the button and the field to call.
I tried creating an outlet from the Todoitem class to the textfield, but xcode won't let me connect it....
To do this, the Todoitem would need to reside in the nib.
But, even if you could do that, why should the model object know about the text field? Carrying values between model and view is a controller's job.

NSArrayController: How to programmatically clear selection?

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...
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() )
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.
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.
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];

Get index of a view inside a NSCollectionView?

I've developed an app for Mac OS X Lion using its new view-based NSTableView, but as I want to port the whole app to Snow Leopard I'm trying to figure out the best way to emulate such a tableview. So far I've created a NSCollectionView and everything is fine, except for the fact that I can't get the index of the view from which a button click event is triggered.
In Lion I have the following function:
- (IBAction)buttonClick:(id)sender
so I can get the index of the view inside the tableview using a method (I can't remember its name) like
- (NSInteger)rowForView:(NSView *)aView
with aView being the sender's superview, but I couldn't find something similar for the collection view ... The only "useful" method seems to be
- (NSCollectionViewItem *)itemAtIndex:(NSUInteger)index
(or something like this), but this can't help me as it returns a NSCollectionViewItem and I can't even access it knowing only the corresponding view!
Within buttonClick, try this code:
id collectionViewItem = [sender superview];
NSInteger index = [[collectionView subviews] indexOfObject:collectionViewItem];
return index;
Hope this helps :)
Geesh! Both of those approaches have issues. I can see how the first on may work, but note that the "collectionViewItem" is actually the view, NOT the collectionViewItem, which is a view controller.
The second way will not work, unless you subclass the button and put in a back link to the collectionViewItem. Otherwise, your view does not know what collectionViewItem controls it. You should use a selector binding to the collectionViewItem's representedObject instead, to get the action to the correct object in your array.
How about something like:
id obj = [collectonViewItem representedObject];
NSInteger index = [[collectionView contents] indexOfObject:obj];
As I suggested here: How to handle a button click from NSCollectionView
I would do it like this (because the button you want to press should be coupled with the corresponding model, therefore the represented object):
Add a method to the model of your collectionViewItem (e.g. buttonClicked)
Bind the Button Target to Collection View Item
While binding set model key path to: representedObject
While binding set selectorname to: methodname you chose earlier (e.g. buttonClicked)
Add protocol to your model, if you must tell delegate or establish observer-pattern
use NSArrayController for binding to NSCollectionView,
use collectonViewItem.representedObject to get a Custom Model defined by yourself.
save and get index in your custom model.
That's works for me.

Binding to a NSViewController's representedObject

(Abstract: bindings work in code, but not in IB)
I have a window managed by a NSWindowController. To the left of the window is a source view. To the right is a table view showing the elements of the currently selected source.
I have set up a NSTreeController within my window XIB. I want its contents to be used for the source view. It's selection will drive the table view.
I am trying to split this up using NSViewControllers. One view controller will load a NIB containing the source view. Another view controller will load the table view.
Seeing that I need access to the NSTreeController within the source view controller, I have set it to be the view controller's representedObject. (Actually for this setup to be done by the time awakeFromNib is called on the view controller, I have turned representedObject into an IBOutlet).
All works fine when I wire my source view up in code:
[outlineView bind:#"content"
toObject:sources
withKeyPath:#"arrangedObjects"
options:nil];
[outlineView bind:#"selectionIndexPaths"
toObject:sources
withKeyPath:#"selectionIndexPaths"
options:nil];
[[outlineView tableColumnWithIdentifier:#"Title"] bind:#"value"
toObject:sources
withKeyPath:#"arrangedObjects.title"
options:nil];
I am however unable to reproduce this using Interface Builder. Thing is, here the "controller key" textfield is grayed out. Thus I bind column's "value" to the file owner using a model keyPath of "representedObject.arrangedObjects.title". This does not show the desired behavior. Actually an exception is thrown: -[NSProxy doesNotRecognizeSelector:_mutatingNodes] called!
How can I use representedObject in IB?
Can I create a controller in IB which acts as proxy to representedObject?
Could I set-up a tree controller in the source view XIB which during NIB loading gets swapped out for the representedObject?
I moved away from using representedObject. It appears that is meant only for model objects.
I now pass in my tree controller using a custom outlet. I continued setting up and tearing down the bindings in code.
I’ve similar issues when I try to pass a reference to an object controller (NSTreeController in my case). I don’t think this is how Apple wants you to use their KVO-compatible controllers. The exceptions look like they’re XIB-unarchiving & timing-related.
The trick is not to pass the controllers, but to pass the underlying data and keep the selection in sync.
This way you can set up your bindings in a storyboard and won’t get any exceptions. You’ll have to set up a new instance of an object controller for every child view controller (copy & paste in Storyboard once you configured the first one works). For a detailed example take a look at another answer that gets much more into detail.

Resources