Bind to NSTreeController selectionIndexPaths - objective-c

I want to bind to a NSTreeController's selectionIndexPaths programatically by doing the following (so that I can get a string a selection and display in a text view)
[activePDFView bind:#"name"
toObject:treeController
withKeyPath:#"selectionIndexPaths.nodeName"
options:options];
The tree controller is bound to a NSMutableArray that contains objects with the "nodeName" property. The object inside the NSMutableArray is KVC compliant for the property "nodeName" since I've implemented the proper accessors.
When I compile, I get the following message
'[<__NSArray0 0x1001698d0>
addObserver:forKeyPath:options:context:]
is not supported. Key path: nodeName'
I am not quite sure but is my binding correct?
Thanks.

It looks like what you want to bind to is not selectionIndexPaths, but instead the selection binding. The selectionIndexPaths binding will return an array of NSIndexPath objects, which is typically only used when binding an outline/browser view's selection to the tree controller. selection actually returns a proxy object which can represent either a single or multiple selection, and passes through any KVC requests to the underlying selected object(s). It's defined in NSObjectController, which is a superclass of NSTreeController. In your case, you would want:
[activePDFView bind:#"name" toObject:treeController withKeyPath:#"selection.nodeName" options:options];

Related

How to bind different entities with NSTableView and NSTabView?

I have an NSArrayController which handles entities of GeometryShape.
GeometryShape has: name, type, color.
LineShape is a GeometryShape and has: beginPositionX, beginPositionY, endPositionX, endPositionY.
CircleShape is a GeometryShape and has: positionX, positionY, radius.
The NSTableView shows all inserted shapes in the NSArrayController, where each column is bound with arrangedObjects & the key name.
When I select a line shape, its properties are displayed in the Line tab - which is the default tab.
Now if I select a circle shape, I want the Circle tab to be selected and the circles properties to be displayed.
…and so depending on which shape type I select the corresponding tab will be selected and display the corresponding shape properties.
How may I achieve this excellent :) model?
I think you'd want to implement a NSTableViewDelegate and programmatically select the appropriate tab within an implementation of tableViewSelectionDidChange: When the selection changes, you just grab the tabView's IBOutlet and assign a new selectedIndex based on the arrayController's selection.
Alternately, you could bind the value of the tabView's selectedIndex to the array controller's selection, but you would need a custom value transformer that converted from the selection id to an NSUInteger that reflects the appropriate class.
In either implementation, you're writing code using isKindOfClass and mapping to an integer.
You may also be able to bind the tab view's selectedLabel to the array controller keypath of selection.class but I'm guessing you still would need a valuetransformer wrapping NSStringFromClass() as described in the NSValueTransformer docs. I'm not totally certain there's a completely non-code way of transforming the class to a string you could bind the selectedLabel to, though.
Personally, I don't love implementing the custom value transformer because you're writing code to allow implementing behavior buried in IB... all to avoid writing code that could live in a custom tableview delegate.

How can an NSCell detect the model key path it's bound to?

Imagine the following:
You have an NSTableView with multiple columns.
Each NSTableColumn is bound to the same NSArrayController (we'll call it myArrayController).
This array controller holds many instances of a model class.
One column has an NSPopupButtonCell where selectedObject is bound to myArrayController.arrangedObject.somePropertyOfTheModel.
The table gets properly populated.
Q: How can an NSCell detect the model key path it's bound to? (somePropertyOfTheModel in this example)
I'm trying to make a cell reusable by not having it assume its represented value is always from somePropertyOfTheModel (could be from somethingElse). Upon a given action, it needs to bind the content of a 2nd controller to somePropertyOfTheModel or somethingElse.
[Edited] A bit more (maybe too much?) explanation:
I'm creating a popup-button which displays a few preset values of a property, and a "Custom Value" item which triggers a PopOver window to allows configuration of the property. I want to make it so that I can drop this cell into a table and having it manage the PopOver much like it already manages its own Menu.
What I've tried:
[self representedObject] returns the actual value. Setting it as content to the 2nd controller is all good and well... but whenever the model's property changes, the 2nd controller won't be notified since it's tied to the actual instance of the value... not a binding to the model property.
Querying the cell's binding gives me nothing:
[[self infoForBinding:#"selectedObject"] objectForKey:NSObservedObjectKey]; // nil returned
[[self infoForBinding:#"selectedObject"] objectForKey:NSObservedKeyPathKey]; // nil returned
Querying the cell's control's (the NSTableView) binding doesn't give me much:
[(NSTableView*)[self controlView] infoForBinding:#"content"] objectForKey:NSObservedObjectKey]; // returns myArrayController or a poxy to it.
[(NSTableView*)[self controlView] infoForBinding:#"content"] objectForKey:NSObservedKeyPathKey]; // returns #"arrangedObject"
// running the same but for #"selectedObject" returns nothing but nils
I'd like to query the NSTableColumn itself -- that's where the bindings are defined in IB -- but cell's aren't aware of their existence (unless I've overlooked something obvious). Even passing via NSTableView, no method returns an NSTableColumn for a given cell (and considering prototype cells, I doubt it would help).

Best way to add/remove to an NSMutableArray managed by an NSArrayController

I've got a NSMutableArray (containing NSMutableDictionary instances) bound to an NSArrayController (the NSArrayController is in turn bound to NSTableView columns).
What is the most Cocoa-, and KVO- friendly way of, programmatically :
adding a new empty object (NSMutableDictionary) to the array?
removing currently selected object? (after removing, the previous item - if exists - should be selected)
I've always been doing this in a way I don't particularly like - and I'm sure it's not the best way around (too many lines of code for something so simple : in Cocoa that indicates a wrong take on the subject :-)).
My code (quite an overkill, actually) :
Adding to the Array
NSMutableArray* oldParams = [paramsArray mutableCopy];
[oldParams addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:#"Parameter",#"Parameter",#"",#"Value", nil]];
[self setParamsArray:oldParams];
[paramsController setSelectionIndex:[paramsArray count]-1];
Removing currently selected object from the Array
if ([paramsArray count]>0)
{
int s = [paramsController selectionIndex];
NSMutableArray* oldParams = [paramsArray mutableCopy];
[oldParams removeObjectAtIndex:s];
[self setParamsArray:oldParams];
if (s<=[paramsArray count]-1)
[paramsController setSelectionIndex:s];
else
[paramsController setSelectionIndex:[paramsArray count]-1];
}
So, what are your opinions on that?
Given that the array controller is bound to a property named paramsArray on some object, the best approach is to define the key-value coding indexed accessors on that object's class. Then, use those accessors to mutate the to-many relationship represented by the property in a KVO-compliant manner.
For example, implement -insertObject:inParamsArrayAtIndex: and then use that to add an object. If you like the convenience of NSMutableArray's -addObject: method, you can write an -addObjectToParamArray method that forwards to -insertObject:inParamsArrayAtIndex:.
By the way, "paramsArray" is a poor name for a property. The property name shouldn't encode the type used to implement it. If you look at the templates for the indexed accessor names, you'll see that Apple is expecting to-many relationship properties to just be a plural noun like "params" (no "Array"). For example, -paramsAtIndexes: is better than -paramsArrayAtIndexes:.
You have to think of your array as the controller's backing store, and that it's managing it for you.
Adding an object:
[[self accountsArrayController] addObject:accountDictionary];
Removing the currently selected object:
[[self accountsArrayController] remove:nil];
You'll have to write another line or two to make that previous item selected, but that's an exercise I leave to you.

Sorting NSTableColumn with NSArrayController

I have a NSArrayController bound to an NSTableView so the table column like the following:
NSTableView bindings:
Content -> ArrayController.arrangedObjects
SelectionIndexs -> ArrayController.arrangedObjects
SortDescriptors -> ArrayController.sortDescriptors
NSTableColumn bindings:
Value -> ArrayController.arrangedObjects.description
When I try and sort it using the column header it just crashes with something like
error setting value for key path sortDescriptors of object NSArrayController
Any ideas?
I struggled with the exact same problem today.
It seems that binding the content and selectionIndexes of the tableView to the array controller IB > inspector window > select your tableView > bindings tab, disables the sorting by clicking on the table header. This makes sense, because the table view now shows you the exact contents (and ordering) of the array controller.
I unchecked these bindings in the IB, also removed any sort keys from the table columns IB > inspector window > select your NSTableColumn > attributes pane. Select the checkbox Creates Sort Descriptor in the table column's binding tab. No sortDescriptor is needed on the table, although I think binding the table's sortDescriptor to Shared User Defaults Controller saves the ordering when you quit your application.
If you need to sort your table, put a sortDescriptor on the array controller, maybe in the awakeFromNib.
- (void)awakeFromNib {
[super awakeFromNib];
[self setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"propertyOfYourObject" ascending:YES selector:#selector(compare:)]]];
}
This does not interfere with clicking the table column headers.
I couldn't get a sortDescriptor on the array controller to work with bindings.
I see several issues in your binding attempt.
One doesn't usually need to bind the NSTableView at all. Binding the values of specific NSTableColumns to the NSArrayController is enough.
You try to bind something against a .description property. Please remember - "description" is like a "reserved word" in Obj-C. Any NSObject should present itself as NSString in its "description" method. This is what is called when you po <object> in the debugger, or pass an NSObject to NSLog via "%#". So... probably you'd want to rename your property to something else.
You do NOT need to bind the sort descriptors of the NSArrayController or the NSTableView or the NSTableColumn at all. As it happens, when you bind an NSTableColumn's value to the NSArrayController's arrangedObjects, the NSTableColumn (actually the NSColumnHeader) object knows to set the NSArrayController's sortDescriptor to the same path as the one you specified for the column's value binding - as you click on the column header. In other words - sorting by clicking on column headers comes free, if you just bind your column's value to the NSArrayController's arrangedObjects.
Documentation on table binding is bad and frustrating. There are several different schemes for working with a Table, and debugging binding problems is a real nightmare. However, there are plenty essays and tutorials on the net for this.
Hope this helps.
Let me suggest you a simple way to do this -
NSTableColumn bindings:
Value ->
Bind to: ArrayController
Controller Key : arrangedObjects
Model Key Path : keyPath (such as name)
If you are new to using bindings with table view, this article will be of great help to you-
EDIT: Project relocated to Github. (No more explanation - code only)
NSTableView, NSArrayController and More Bindings

How to show calculated values in NSTableView?

I have an NSTableView that binds via an NSArrayController to an NSMutableArray of NSString's.
There's also a TextView in the same window. What I want to do is display a number of occurrences of each NSString in the NSTableView.
I've thought of a way to do this, but that doesn't look elegant way of doing this:
Inherit from NSString and define new method that performs a search in predefined object (NSTextView) and returns the number of occurrences.
I'm guessing there must be a more natural way of achieving the same result?
EDIT:
Sorry, should have clarified. NSSMutableArray is an array of NSObjects that have an NSString property. I suppose I could define an extra method (findAllOccurencesOfString:inString:) which would return a number. But the question is how do I bind to this function and in that binding how to I pass a var (pointer to textField)??
You'll need to have a wordCount (read only) property on whatever objects are in your table data source, this will have to call your new method internally using the object's own string value, as you can't pass parameters in bindings (unless they've changed, I haven't used bindings for a while as I've been concentrating on iOS). Then bind to this property for the column in the table. Presumably you don't need to pass the pointer to the textfield as there is only one?