Sorting NSTableColumn contents - objective-c

I have a problem with sorting NSTableColumn contents. In my NSTableView there are three columns: File, Size, Path. The contents are stored in NSMutableArray. Each object in this array is a NSDictionary containing three keys: file, size and path - value for each is a NSString.
In Interface Builder, in each Table Column's attributes I can choose sorting options:
Selector: IB entered "compare:" which I think is ok, because I compare NSStrings.
Sort Key - and that's the problem I think - I don't know what to enter here.
Any clues? If you've got questions about my code, please ask.

So I found the complete solution.
First, go to Interface Builder. Select column that you want to sort. Go to the column's inspector and choose the first button to see the "Table Column Attributes". Set appropriate values (literally, no " or ' or # are needed):
Sort key: file
where 'file' is the key of dictionary that contents is shown in your column.
Selector: compare:
standard sort function.
Now, save all the changes here and jump to Xcode, to the class in which is the model, source of the data shown in NSTableView. You should already know that you need two methods there:
-(NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
-(id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn
*)tableColumn row:(NSInteger)row
these two are needed to conform the NSTableDataSource informal protocol. You can read about it at the MacDev Center.
Now all you have to do is to add a new method:
-(void)tableView:(NSTableView *)tableView sortDescriptorsDidChange: (NSArray *)oldDescriptors
it can contain a very simple code that will do the thing:
-(void)tableView:(NSTableView *)tableView sortDescriptorsDidChange: (NSArray *)oldDescriptors
{
NSArray *newDescriptors = [tableView sortDescriptors];
[results sortUsingDescriptors:newDescriptors];
//"results" is my NSMutableArray which is set to be the data source for the NSTableView object.
[tableView reloadData];
}
And that's all. It's working in my app, hope it will work in your case :D Everything should work automatically. I've just saved all files and Built the app. And it worked. :)
Site that helped me:
CocoaDev: SortingNSTableView

Key is the key that you use in dictionary to retrieve the value.
In your case you have three keys: file, size and path. Select the one on which you want to sort. The key is used to retrieve value from each record to be used for sorting.
If your keys are #"file", #"size", #"path" and you want to sort on file then try to put value.file into the Sort Key field in IB.
Use keys that you use when inserting values into your NSDictionary.

Related

Possible implementations of editable UITableView data sources

I'm seeking to implement a UITableView that has sections representing the recent history and future queue of a media player. It seems to me that a Queue-type structure would be most applicable for this, as this represents the nature of the operation, but the requirement that the future queue part be editable poses some challenges.
I think that a linked list would be the best option here to store the media representations, as opposed to a vector type structure where all of the elements are stored contiguously. It seems to me that, in the case of moving or removing an object within the queue and adding object at the end, a linked list is more efficient than a vector, as simply assigning a few pointers different values seems more lightweight than moving entire chunks of memory. The internal implementation details of NSMutableArray seem quite obscure but I'm assuming it's a vector type.
However, I've never really seen a true linked-list implementation in Objective-C. Furthermore, the structure of UITableViewDataSource, requiring cellForRowAtIndexPath: to be called with a specific row rather than simply iterating through the list, exposes the weakness of linked list implementations, as seeking a specific index can be expensive. This would be irrelevant if cellForRowAtIndexPath: is only called in order, but it seems reckless to ignore the indexPath parameter in favor of just iterating through the list.
How are these structures typically implemented? Is iterating through a link list as proposed a bad idea?
Since Objective-C doesn't have an explicitly defined "linked list" object type, the closest alternative would be NSMutableArray.
You should never assume the order in which
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;
will be called, and if you implement the UITableView correctly, it should only be called for the index paths of cells right before they appear on screen (thus the order it's called would alter based on the direction the user is scrolling in).
I recommend that you create a 2-dimensional NSMutableArray with the 1st representing the sections in the table and the 2nd representing the rows in each section. You would then initialize the array using something like:
self.sectionsArray = [NSMutableArray array];
[self.sectionsArray addObject:[NSMutableArray array]]; // history rows
[self.sectionsArray addObject:[NSMutableArray array]]; // queued rows
Which would allow you to easily retrieve the stored items using something along the lines of:
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger section = indexPath.section;
NSUInteger row = indexPath.row;
id fetchedObject = self.sectionsArray[section][row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellReuseID"];
// perform setup here based on the fetchedObject
return cell;
}
I've listed some NSMutableArray methods you may find helpful below:
- (void)addObject:(ObjectType)anObject;
- (void)insertObject:(ObjectType)anObject atIndex:(NSUInteger)index;
- (void)removeObject:(ObjectType)anObject atIndex:(NSUInteger)index;
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(ObjectType)anObject;
- (void)removeLastObject;

Single NSTableView with multiple data sources

Does it make sense logically to have two data sources populating a single NSTableView? Or would a pop up button (with ability to choose from which data source to read)be more reasonable?
If I go with the single NSTableView option, would I be better off if I input all the data into a single data source (say an NSDictionary) and then populating the table? The only problem that I see with this idea is what happens when a selection of the table is done.. How would I decipher which of the original location was selected.
Another idea/potential problem that just popped into my head ... if I take the data from the data source and populate the table directly, how would give the count of the table in the relevant NSTableView 'count' method.. Would adding the count of data store 1 and data store 2 do it?
Sorry if it's a bit jumbled up.. Thanks for any input!
You can definitely use multiple data sources for the data to be displayed in the table view, but they must all be funnelled through a single controller object that is assigned as the table view's datasource and which implements the NSTableViewDatasource protocol.
You will have to write some code in your controller object so that you keep track of the multiple source arrays that make up and supply the table view with the appropriate values for the number of items and the content of each item.
This is a very simplistic example:
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [array1 count] + [array2 count];
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
if(rowIndex >= [array1 count])
{
return [array2 objectAtIndex:rowIndex - [array1 count]];
}
else
{
return [array1 objectAtIndex:rowIndex];
}
}
As for selection, well, your controller knows where it's getting its data from, so when the user selects a particular row it should be trivial to translate that row index to a matching object in one of your backing stores.
Depending on your application logic, it can make sense. The easiest way would be to set up a different source for every table section. Then, you could use the section number as a selector to your data base.

Sorting a column in NSOutlineView that is bound to NSTreeController

I have an NSOutlineView that is bound to an NSTreeController.
In Interface builder, I have bound each column of NSOutlineView to the tree controller with the Controller key "arrangedObjects" and model key path as the entity attribute or a method in my entity class.
Now all other columns sort perfectly except for one special column. The special column has a model key path bound to a method that is declared in my Entity class. This method depending on some condition in my code will return either NSString or NSDictionary.
When it returns an NSDictionary, the delegate method:
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
parses the NSDictionary and returns a double value for that cell. Also note that the cell in this case is derived from NSCell and displays a progress bar.
In short, my special column will display a mix of progress bars and strings depending on the situation.
I would like to implement sorting so that all progress bars stay together and the strings are sorted alphabetically.
Perhaps you will have some luck if you try setting the table column's sort descriptor to a descriptor you create with a comparator that looks at the objects' classes. (You could also try filling in the "Sort Key" in IB, using the "class" key, but I think this might be less likely to work...)
Well i found a solution to this myself and just 2 minutes after posting my query :) The solution was to add a custom sort key in IB and define a method with that key name in my Entity class that returns an NSInteger. Since i want all NSDictionary objects to stay together and all NSStrings to be sorted, i return appropriate integer based on the object type.
Note for anyone who might be stuggling with sort not working: Make sure binding for sortDescriptor is enabled in IB.
try
NSSortDescriptor *sorter = [[[NSSortDescriptor alloc]
initWithKey:NULL
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)] autorelease];
[[myOutlineView tableColumnWithIdentifier:#"Name"]setSortDescriptorPrototype:sorter];

Populating TableView line by line in Obj-C

I'm looking for a way to populate a table view from one single document, namely I want to load a .po file.
I would like each line of my table view to load one line of text from the PO file.
Ideally, I would like to have one line in the first column, and the corresponding translation in the second column (to get a clear view of the contents).
I have not worked much with table views yet so please forgive my ignorance!
I have done my research but I find the apple documentation confusing and very unclear -- and didn't find much online...
Thanks in advance for any help!
bbum is correct, you don't push data to your table, you provide it and the table displays it. Friday I did a quick mock-up on putting a text file displayed line by line, so maybe some of the code can help some.
Get a table view connected with an outlet to it's data source, then you can do something like this:
// Class variable in your table delegate object
NSArray* lineList;
IBOutlet NSTableView* table;
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [lineList count];
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
return [lineList objectAtIndex:row];
}
// Be sure to use the proper encoding for your text file
// do something like this to load your text file.
- (void) loadData:(NSString*)ourPath
{
NSError* err = nil;
NSString* fullFileText = [NSString stringWithContentsOfFile:ourPath encoding:NSMacOSRomanStringEncoding error:&err];
if (err)
NSLog(#"Err: %#, %d", [err localizedDescription], [err code]);
if (fullFileText)
{
lineList = [[fullFileText componentsSeparatedByString:#"\n"] retain];
[table reloadData];
}
}
In your case you may want to hold an array of dictionaries, using a different key for both versions of your text. That way you can have two columns. The NSTableColumn will tell you which column you will be drawing into when tableView:objectValueForTableColumn: gets called. The other option you have here is making a custom cell that has two fields in it but that's probably overkill for what you're asking.
Note also that there are a number of other optional delegate calls you can add for more flexibility of how you show your data.
Additionally for more dynamic complex tasks I've found that bindings are better. They can be confusing if you're not comfortable with them though. For simple tables it's often just as easy to go this route. Good luck!
You don't push data to a table view, it pulls data from you. This can be done with either bindings or by implementing the table view data sour (which are a little bit different, but mostly the same, between the NS* and UI* platforms).
The NSTableView and UITableView documentation both have links to examples and programming guides. Read those and if you still don't get it, ask a specific question.

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.