20

I have anNSTableView showing the contents of a directory. I watch for FSEvents, and each time I get an event I reload my table view. Unfortunately, the current selection then disappears. Is there a way to avoid that?

jscs
  • 63,694
  • 13
  • 151
  • 195
AP.
  • 5,205
  • 7
  • 50
  • 94

5 Answers5

13

Well, you can save selection before calling reloadData and restore it after that.

NSInteger row = [self.tableView selectedRow];
[self.tableView reloadData];
[self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];

This worked for me in some cases. But if you insert some items BEFORE the selected row, you should andjust your row variable by adding count of added items to it.

silvansky
  • 2,426
  • 2
  • 19
  • 20
  • What is the behavior if the selected index is no longer valid (larger than the number of items) after the data is reloaded? – wcochran Jul 20 '22 at 03:50
12

Swift 4.2

Create an extension and add a method which preserves selection.

extension NSTableView {

    func reloadDataKeepingSelection() {
        let selectedRowIndexes = self.selectedRowIndexes
        self.reloadData()
        self.selectRowIndexes(selectedRowIndexes, byExtendingSelection: false)
    }
}

Do this in case you use the traditional way of populating table views (not NSArrayController).

pkamb
  • 33,281
  • 23
  • 160
  • 191
nsinvocation
  • 7,559
  • 3
  • 41
  • 46
  • 1
    small note, this will select the first & previous selected item, set "byExtendingSelection" to false if you only want the previously selected item – Steven B. Jan 12 '17 at 21:59
6

In the case of using Data Source, Apple Documentation in the header file on reloadData() is that

The selected rows are not maintained.

To get around, you can use reloadDataForRowIndexes(rowIndexes: NSIndexSet, columnIndexes: NSIndexSet). As mentioned in the same header file

For cells that are visible, appropriate dataSource and delegate methods will be called and the cells will be redrawn.

Thus the data will be reloaded, and the selection is kept as well.

Harry Ng
  • 1,070
  • 8
  • 20
6

It depends on how you populate your NSTableView.

If you have the table view bound to an NSArrayController, which in turn contain the items that your table view is displaying, then the NSArrayController has an option to preserve the selection. You can select it (or not) from within Interface Builder as a property on the NSArrayController. Or you can use the setPreservesSelection method in code.

However, if you completely replace the array of items that the NSArrayController manages each time you get your FSEvents, then maybe the preservation of selection cannot work. Unfortunately the Apple docs on this property of NSArrayController are a bit vague as to when it can and cannot preserve the selection.

If you are not using an NSArrayController, but maybe using a dataSource to populate the table view, then I think you'll have to manage the selection yourself.

pkamb
  • 33,281
  • 23
  • 160
  • 191
OzBandit
  • 1,044
  • 7
  • 14
4

A variant on @silvansky's answer.

This one has no need to keep track of count of items inserted/deleted. And it maintains multiple selection.

The idea is to...
1. create an array of selected objects/nodes from the current selection.
2. refresh the table using reloadData
3. for each object obtained in step 1, find/record it's new index
4. tell the table view/outline view to select the updated index set

- (void)refresh {
    // initialize some variables
    NSIndexSet *selectedIndexes = [self.outlineView selectedRowIndexes];
    NSMutableArray *selectedNodes = [NSMutableArray array];
    NSMutableIndexSet *updatedSelectedIndex = [NSMutableIndexSet indexSet];

    // 1. enumerate all selected indexes and record the nodes/objects
    [selectedIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
        [selectedNodes addObject:[self.outlineView itemAtRow:idx]];
    }];

    // 2. refresh the table which may add new objects/nodes
    [self.outlineView reloadData];

    // 3. for each node in step 1, find the new indexes
    for (id selectedNode in selectedNodes) {
        [updatedSelectedIndex addIndex:[self.outlineView rowForItem:selectedNode]];
    }

    // 4. tell the outline view to select the updated index set
    [self.outlineView selectRowIndexes:updatedSelectedIndex byExtendingSelection:NO];
}
Brad G
  • 2,528
  • 1
  • 22
  • 23
  • Brad, Did you try this? reloadData is lazily loaded so you won't have the updated data after the call. To test try this code after a drag / move operation which changes the order of the rows. I don't think it will produce the desired result. – Steve Sheldon Jan 25 '17 at 18:11
  • @SteveSheldon I do recall this working, but unfortunately this project no longer runs so I cannot test anymore. – Brad G Mar 03 '17 at 15:28
  • @SteveSheldon, I think this works; I have used a similar technique; and if I recall, Apple's docs even mention this. Which updated data are you referring to? and after which call? – Jerry May 02 '17 at 07:13