How to show calculated values in NSTableView? - objective-c

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?

Related

Creating an array item with a property that's dependent on an aggregate function on the array itself

Suppose I have an NSArrayController containing a number of 'blob' objects. These objects are displayed in a view-based NSTableView via bindings.
Now suppose that each 'blob' object contains a property called amount. For one of the NSViews in each row of the table I'd like to display amount / max_amount_in_array.
In other words, I somehow need to bind my cell to the NSArrayController's arrangedObjects.#max.amount and to the NSTableViewCell's objectValue.amount at the same time and perform my calculation.
Is there a way to handle this nicely using bindings?
Currently the only idea I have to is to have a ratio property in 'blob' and recalculate it myself every time that an object is added to the array. That's quite possible, but it just seems like there should be a more bindings-like way to solve the problem.
I've now done this like so:
[libraryCell.myView bind:#"amount"
toObject:libraryCell
withKeyPath:#"objectValue.amount"
options:nil];
[libraryCell.myView bind:#"max"
toObject:_librariesController
withKeyPath:#"arrangedObjects.#max.amount"
options:nil];
There are thus two properties in myView (amount and max) and when either of them change I do the calculation and update the display accordingly.

Implementing a category within the implementation of another interface in Obj-C

I have a custom class in Obj-C called RouteManager which contains an array of NSStrings. Each string is a bus stop name which is used as a key for a dictionary to get the rest of the information for the bus stop (basically, just [busStopDictionary allkeys]). In one of the situations where my app uses this array, I want to return the array sorted by the distance from the user. I've started setting up the code to be able to call sortedArrayUsingSelector on my array with the following method:
- (NSComparisonResult)compareByDistance:(NSString*) otherStop
{
// Return appropriate NSOrdered enum here based on comparison of
// self and otherStop
}
My problem is that in the case where compareByDistance is a method of RouteManager, self refers to the instance of RouteManager. However, I need self to refer to the NSString that the compare is being called on. So, I assumed I needed to setup a category, as such:
#interface NSString (Support)
-(NSComparisonResult) compareByDistance:(NSString*)otherStop;
#end
This got my self reference correct, however this comparison uses values from the RouteManager class. When implemented as seen above, the NSString (Support) implementation obviously complains that those values are undeclared.
That should provide enough background info for my question. How do I go about doing this? I would like my category of NSString, which consists solely of the method compareByDistance, to be able to use values from the current instance of my class, RouteManager, which inherits from NSObject. Ideally, I feel as though the category should somehow be within RouteManager. I feel there has to be some way to accomplish this that is cleaner than passing the necessary values into compareByDistance. Thanks in advance for any and all help.
Your best bet would be to define a custom class for a bus stop, instead of storing them as strings and dictionaries.
Make the BusStop class have properties for Name, Location and whatever else. Implement the compareByDistance: method on the BusStop class.
You can still use a dictionary if you need to look them up by name. Just store them with the name as the dictionary's key, and the BusStop object as the dictionary's value.

Populating NSTableview from a mutable array

I've been attempting this for two days, and constantly running into dead ends.
I've been through Aaron Hillegass's Cocoa Programming for MAC OS X, and done all the relevant exercises dealing with NSTableview and mutable arrays, and I have been attempting to modify them to suit my needs.
However none of them seem to be using an array with objects as a data source, it seems to use the tableview as the datasource.
I'm trying to implement Jonas Jongejan's "reworking" of my code here, with a Cocoa front end to display the results.
Any pointers or suggestions I know this should be simple, but I'm lost in the wilderness here.
I can populate the table by setting the array
It's pretty simple really, once you get to understand it (of course!). You can't use an NSArray directly as a table source. You need to either create a custom object that implements NSTableViewDataSource or implement that protocol in some existing class - usually a controller. If you use Xcode to create a standard document based application, the document controller class - (it will be called MyDocument) is a good class to use.
You need to implement at least these two methods:
– numberOfRowsInTableView:
– tableView:objectValueForTableColumn:row:
If you have a mutable array whose values you'd like to use in a table view with one column, something like the following should do as a start:
– numberOfRowsInTableView: (NSTableView*) aTableView
{
return [myMutableArray count];
}
– tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
return [myMutableArray objectAtIndex: rowIndex];
}
It has just occurred to me that you could add the above two methods as a category to NSArray replacing myMutableArray with self and then you can use an array as a data source.
Anyway, with a mutable array, it is important that any time you change it, you need to let the table view know it has been changed, so you need to send the table view -reloadData.
If your table view has more than one column and you want to populate it with properties of objects in your array, there's a trick you can do to make it easier for yourself. Let's say the objects in your array are instances of a class called Person with two methods defined:
-(NSString*) givenName;
-(NSString*) familyName;
and you want your table view to have a column for each of those, you can set the identifier property of each column to the name of the property in Person that that column displays and use something like the following:
– tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
Person* item = [myMutableArray objectAtIndex: rowIndex];
return [item valueForKey: [tableColumn identifier]];
}
If you replace valueForKey: with valueForKeyPath: and your Person class also has the following methods:
-(Person*) mother;
-(Person*) father;
-(NSString*) fullName; // concatenation of given name and family name
you can add table columns with identifiers like: father.fullName or mother.familyName and the values will be automatically populated.
You could go the datasource route and do all of the heavy lifting yourself, or you could let bindings do all the heavy lifting for you. Add an NSArrayController to the nib file that has the table view in it. Make sure that the File's Owner of the nib is set to the same class that has the mutable array in it. Bind the contentArray of the array controller to File's Owner.myMutableArray. For each column bind Value to the array controller arrangedObjects and add the appropriate key path. This will allow you to get things like user sorting for free if you ever need it.
On the iPhone (I know you're talking about Mac, but maybe this could help) you have to use delegation for loading a tableView. It asks for a cell and you use your array to fill-in the data where needed.
I'm not sure if this works for the Mac, but it'd be worth looking into.
Maybe set dataSource to self and use those delegate methods to access your array based on the row and column #
Apple has a whole guide for Table View Programming so I suggest you start with the Using a Table Data Source section of the that guide.

How to get NSFormatter subclass to work with NSTableColumn sort key and selector?

My setup:
I have a sqlite database from which I populate a NSMutableArray of NSDictionary objects this is the DataSource for my NSTableView.
One of the columns holds "time", the time is a float that holds seconds.
I would like to display the values in this column as minutes:seconds. For instance my data would be 123.4329387 I want to display 2:03 which I have no problem doing with a subclass of NSFormatter (or NSNumberFormatter) applied to my NSTextField in the column.
I have sorting set up by using the Table Column Attributes in IB, I just have the sort key set to "time" and the selector set to "compare:" which works fine without the formatter.
Currently this gives me something like this when I sort (descending)
1:37, 1:31, 0:10, 0:10, 0:09, 1:30, 1:30, 1:26, 0:09
and similar nonsense, it looks like something is going on but it's definitely not sorted.
How do I get the sort to look at the underlying data instead of the formatted value? Alternately, how do I specify a custom sort method and where do I put the code for said method? I have searched around quite a bit and have not found anything to help me out with this problem, any help with this is most appreciated.
It turns out that when it was sorting it was using compare: from the NSString class but it was using the underlying numeric data. I'm not exactly sure what all is going on under the hood here but I managed to fix this by creating my own compare method in a category of NSString called "timeCompare". Then in interface builder I just put "timeCompare:" in the selector field for the the table column. This gets it to call my custom compare method from which I just converted the strings given to NSNumbers and call compare: on them.

How to do string manipulation in an NSValueTransformer?

I have a table column that I'm binding to a value in an NSArrayController. What I'm trying to do is display only a substring of the actual value in the array controller. The way I've been trying to do that so far is by creating an NSValueTransformer subclass, and then doing the string manipulation in the transformedValue method. However, I can't figure out how to get the incoming value turned into a string (it's of type NSConcreteValue), and maybe there's an easier way to do this without value transformers.
Sounds like you're doing presentation-side formatting, in which case you should be using a formatter instead.
Then again, if this is a string containing multiple values (e.g., something like “from 42 to 100”), you should make a model object from it instead, and store those in the array controller. Then you can bind your table columns to specific properties of the model objects, and not have to worry about picking the string apart and then reassembling it (except when you load and later save the model).
Edit: Never mind; I didn't see that the object values are NSValues, not NSStrings.
You can get a string representation of any object using the -description method, but for instances of NSValue it's unlikely to print anything especially meaningful. In other words, it's up to your value transformer to interpret the passed-in object and produce a string. If it's an NSValue instance, the question is what type of data that instance contains. Once you know that, you can write code to represent it as a string (similar to NSStringFromRect()).