Question

Context

I have a 2 column, cell based NSTableView managed by an NSArrayController.

  • The first column is populated with checkboxes to indicate whether the value is enabled.
  • The second column represents the name of the element.

The view is linked to an NSSearchField so is filtered based on a predicate that compares the search string to the strings in column 2.

The view is displaying correctly and filters as intended when the search string is changed.

Requirement

I need to only allow the users to enable a maximum of 5 rows. To accomplish this, I am monitoring the backing array for changes and keeping track of the amount of enabled entries. If the count exceeds 5, then I inform the users with an alert and deselect the previously enabled entry by getting the selected row index and then disabling that index in the backing array. I do this in a [willChange.. ..didChange] KVO block.

This works as intended when the view isn't sorted, but obviously causes issues when it is because the selected row does not correspond to the appropriate index in the array anymore.

Therefore, I use the following code to get the string value of the selected cell if the view is filtered (Search string is not empty). Then I traverse the array and disable the entry matching the string.

NSInteger selectedRow = [_tv_TableView selectedRow];
NSTableColumn* column=  [_tv_TableView tableColumns][1];
NSCell* cell = [column dataCellForRow:row];
NSString* cellValue = (NSString*)[cell stringValue];
return cellValue;

The Problem

Although the selected row is always accurate, getting the value from the representative cell is not. It appears that initially the returned value is correct, but then selecting other rows returns data from the following row even though the selected index is still correct. I have also observed it returning values from several rows down in the view.

Update

After further testing it appears that for the first time a filter is applied it will work, but filtering by a different string retains the cell value of the first selected cell for that particular falter.

i.e:

  1. Load window and choose 6 items
  2. 6th item deselected and user warned
  3. Apply filter
  4. Choose new 6th item
  5. 6th item deselected and user warned
  6. Choose different 6th item in same filter
  7. 6th item deselected and user warned
  8. Enter new filter
  9. Choose new 6th item
  10. 6th item deselected and user warned
  11. Choose new 6th item from same filter
  12. Selected index is correct, but value for selected row is the same as previous selection
  13. User repeatedly warned because correct entry is not being deselected, resulting in 6 permanently selected entries.

However, there are still occasions where the following row value is returned without it being the previously selected item.

Question

My questions are:

  1. Am I obtaining the selected row in the correct way for a filtered list?
  2. How can I consistently retrieve the string value of column 2 based on the selected row, even if the list is sorted?

Thank you for your time.

Solution

I resolved this by changing the code that gets the cell from:

NSInteger selectedRow = [_tv_TableView selectedRow];
NSTableColumn* column=  [_tv_TableView tableColumns][1];
NSCell* cell = [column dataCellForRow:row];
NSString* cellValue = (NSString*)[cell stringValue];
return cellValue;

To:

NSCell* cell = [_tv_TableView preparedCellAtColumn:1 row:row];
NSString* cellValue = (NSString*)[cell stringValue];
return cellValue;
Was it helpful?

Solution

The easy solution is to bind the table's selectionIndexes binding to the array controller's property of the same name. Then, you can access the array controller's -selectedObjects to find, well, the selected objects.

When you have a row (or index) from the table, whether it's selected or not, you can use the -arrangedObjects of the NSArrayController. That will always correspond to the indexes.

Cells are reused. There's not a single cell associated with each (row, column) location. There's a cell associated with each column, but it's reconfigured for each row each time a row needs to be drawn or otherwise manipulated. That's why you need the prepared cell. That said, the cells are conceptually at the view layer (in Model-View-Controller) terms. What you're doing should be in the controller and model domain, so you shouldn't consult the view layer (and shouldn't need to).

OTHER TIPS

For some reason, the internal representation of the items was not being updated when changing the filter in the NSSearchField. This was returning values inconsistent with the actual view.

I resolved this by changing the code that gets the cell from:

NSInteger selectedRow = [_tv_TableView selectedRow];
NSTableColumn* column= [_tv_TableView tableColumns][1];
NSCell* cell = [column dataCellForRow:row];
NSString* cellValue = (NSString*)[cell stringValue];
return cellValue;

To:

NSCell* cell = [_tv_TableView preparedCellAtColumn:1 row:row];
NSString* cellValue = (NSString*)[cell stringValue];
return cellValue;

Prior to this, I also attempted forcing the view to update manually using:

[_tv_TableView reloadData];

But this had no effect on the outcome. I would still be interested to know exactly why this was happening. If I find out, then I'll update the answer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top