1

I'm loading a CSV file that I know nothing about except that the first row are column headers, which could be anything. The number of columns is unknown too. I tried to add a sortDescriptorPrototype to each column and when I do I get up/down arrows visible in the TableView.

    for(NSTableColumn * col in self.tableView.tableColumns){
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey: col.title ascending: YES];
    col.sortDescriptorPrototype = sortDescriptor;
}

Each time I click on the column sortDescriptorsDidChange is called:

- (void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray *)oldDescriptors
    {
        [_data sortUsingDescriptors: [tableView sortDescriptors]];
        [tableView reloadData];
    }

_data is a NSMutableArray where data is stored and first row are the header names. Each row is another NSArray Unfortunately I get this error:

2017-04-17 12:35:09.979142+0200 Table Tool[20954:2577488] [General] [<__NSCFString 0x610000474840> valueForUndefinedKey:]: this class is not key value coding-compliant for the key beds.
2017-04-17 12:35:09.982161+0200 Table Tool[20954:2577488] [General] (
    0   CoreFoundation                      0x00007fffb31a837b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x00007fffc7f9c48d objc_exception_throw + 48
    2   CoreFoundation                      0x00007fffb31a82c9 -[NSException raise] + 9
    3   Foundation                          0x00007fffb4c83e5e -[NSObject(NSKeyValueCoding) valueForUndefinedKey:] + 226
    4   Foundation                          0x00007fffb4b55d9c -[NSObject(NSKeyValueCoding) valueForKey:] + 283
    5   Foundation                          0x00007fffb4b59aac -[NSArray(NSKeyValueCoding) valueForKey:] + 467
    6   Foundation                          0x00007fffb4b55b79 -[NSArray(NSKeyValueCoding) valueForKeyPath:] + 448
    7   Foundation                          0x00007fffb4b7cc9f _sortedObjectsUsingDescriptors + 371
    8   Foundation                          0x00007fffb4be94f7 -[NSMutableArray(NSKeyValueSorting) sortUsingDescriptors:] + 468
    9   Table Tool                          0x00000001000051d8 -[Document tableView:sortDescriptorsDidChange:] + 184
    10  AppKit                              0x00007fffb0f05425 -[NSTableView setSortDescriptors:] + 260
    11  AppKit                              0x00007fffb13c9fbc -[NSTableView _changeSortDescriptorsForClickOnColumn:] + 480

Obviously I have read a lot on the topic, but all examples assume that you know column names at design-time. I'm interested in a solution with NSSortDescriptor and sortUsingDescriptors in Obj-C or Swift, some explanation on sortDescriptorPrototype and why you need it in the first place, since sortDescriptor changes when I click on the column. Is there anything in KVC I can use for the sorting key, like "self" (there is ...valueForKeyPath:@"@sum.self"...)?

Some useful links:

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TableView/SortingTableViews/SortingTableViews.html

https://www.raywenderlich.com/143828/macos-nstableview-tutorial

  • If someone can explain memory management in TableView, that would great. Is the data copied from my _data (NSMutableArray) into TableView? What if you have millions of rows, you don't want to have two copies of the data. – Piotr Kundu Apr 17 '17 at 12:26
  • Are you sure this isn't caused by an unconnected outlet on your storyboard/NIB? – Alex S Apr 17 '17 at 13:39
  • I'm sure, but I'm new to KVC. – Piotr Kundu Apr 17 '17 at 15:02

1 Answers1

0

Here is a completely moronic way of solving it that partly works: First I extract the column index with this method:

-(NSUInteger)getColumnIndexFromTableViewSortDescriptor {
    for(NSUInteger i = 0; i < _maxColumnNumber; i++){
        if(_tableView.tableColumns[i].title == _tableView.sortDescriptors[0].key){
            return i;
        }
    }
    assert(false && "Missing column name");
    return 0;
}

Then I sort the array in-place with sortUsingComparator. Surely this has to be the "wrong". It's completely idiotic to use sortDescriptors if they are not used, other than to figuring out which column was clicked. I guess the click event could figure that out too?

-(void)tableView:(NSTableView *)tableView sortDescriptorsDidChange: (NSArray *)oldDescriptors
{
    NSUInteger idx = self.getColumnIndexFromTableViewSortDescriptor;
    if(tableView.sortDescriptors[0].ascending){
        [_data sortUsingComparator: ^(id obj1, id obj2) {
            return [[obj2 objectAtIndex:idx] compare:[obj1 objectAtIndex:idx]];
        }];
    } else {
        [_data sortUsingComparator: ^(id obj1, id obj2) {
            return [[obj1 objectAtIndex:idx] compare:[obj2 objectAtIndex:idx]];
        }];
    }
    [tableView reloadData];
}

Now the CSV data I'm importing the data in the columns is not uniform, so it could be a NSNumber, followed by three NSDecimalNumber and a few NSString. Partly that's a problem with the data and/or the CSV parser I'm using (ripped from the web, but I will rewrite the sucker later).

So you could end up comparing NSDecimalNumber with NSString which will generate errors like:

unrecognized selector sent to instance

So I continued my stupidity with:

@interface NSDecimalNumber (CompareString)
-(NSComparisonResult)compare:(NSString *)string;

@end

@implementation NSDecimalNumber (CompareString)

-(NSComparisonResult)compare:(NSString *)string{
    NSString *decimalString = self.stringValue;
    return[decimalString compare:string options:NSCaseInsensitiveSearch];

}

Now if you compare two NSDecimalNumber the above will be called and the parameter "string" NSDecimalNumber, even though "string" declared NSString, resulting in "unrecognized selector sent to instance" once again.

  • Apparently the block method above is supposed to be faster, any real life performance measurements, you guys? http://stackoverflow.com/questions/805547/how-to-sort-an-nsmutablearray-with-custom-objects-in-it/805587 – Piotr Kundu Apr 17 '17 at 19:30