save selected row in UITableView after reloadData

Viewed 29851

I write custom jabber client in iphone.

I use xmppframework as engine.

And I have UITableViewController with NSMutableArray for repesent contact list.

When i receive(or somebody change it contents) roster (aka contact list) i wanna change UITableView items (add/remove/modify). So if User work with listView at time when list updates by

[items addObject:newItem];
[self.tableView reloadData];

user lost current selection item.

So, my question is howto save (if possible, i mean if given selected item not removed) current select item after reloadData?

Thx.

14 Answers

Swift 4.2 Tested

The correct way to update selected rows after reload table view is:

let selectedRows = tableView.indexPathsForSelectedRows

tableView.reloadData()

DispatchQueue.main.async {
    selectedRows?.forEach { selectedRow in
        tableView.selectRow(at: selectedRow, animated: false, scrollPosition: .none)
    }
}

On iOS 9.3 and Swift 2.x, I simply had to call the function on the main thread :)

self.tableView?.selectRowAtIndexPath(indexPath, animated: false, scrollPosition: .None)

Edit:

After testing, it doesn't always work!,

the reason is the index may change !

so the correct way is
make new variable

var currentIndex : IndexPath?

and on didSelectRow

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        currentIndex = indexPath
}

then change the function to

 func reloadTableView() {
        let indexPaths = tableview.indexPathsForSelectedRows
        if let currentIndex = self.currentIndex {
        self.tableview.reloadRows(at: [currentIndex], with: .fade)
        for path in indexPaths ?? [] {
            tableview.selectRow(at: path, animated: false, scrollPosition: .none)
            }}
    }

==========

@marmor Answer worked

this is a Swift version of his code

Swift 4.2.3

func reloadTableView() {
        let indexPaths = tableView.indexPathsForSelectedRows
        tableView.reloadData()
        for path in indexPaths ?? [] {
            tableView.selectRow(at: path, animated: false, scrollPosition: .none)
        }
    }

I solved it better by declaring an array to store, let's say, the ID or String Text of the cell you have selected, and adding it on the didSelectItemAtIndexPath function. This way, even if the number of rows changes, the selected ones won't. For example:

var selectedItems: [String] = [] // This array type really depends on your preference and what property you're using to store
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    guard let cell = collectionView.cellForItem(at: indexPath) else { return }
    selectedItems.append(cell.textField.text)
}

And on your initial cellForItemAtIndexPath

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath)
    if let text = cell.textField.text {
        if selectedItems.contains(text) {
            collectionView.selectItem(at: indexPath, animated: true, scrollPosition: [])
        }
    }
    return cell
}

my two cents for UICollectionView: (credits to Basil..)

I do run in main thread as I reload from a background task:

final override func safeReloadData(){

            DispatchQueue.main.async { [weak self] in

                guard let self = self else {
                    return
                }

                let indexPaths = self.collectionView.indexPathsForSelectedItems
                self.collectionView.reloadData()
                for path in indexPaths ?? [] {
                    self.collectionView.selectItem(at: path, animated: false, scrollPosition: [])
                }
            }
    } 

A lot of these answers are basically using hacks to re-select the row that was previously highlighted by finding out what the previously selected row was from the table view which seems like a bad way to go about it, because usually when your tableview reloads, there is a change in the data that is populating the tableview.

So rather base you re-selection of the row based on your data

let data = ["some", "array", "of", "data"]
let selected = "of" // this should be populated from didSelectRowAt indexPath:

tableView.reloadData()

if let index = data.firstIndex(of: selected) {
  tableView.selectRow(at: IndexPath(row: index, section: 0), animated: false, scrollPosition: .none) // the "of" row should now be highlighted, regardless if the data changed order
}
Related