开发者

save selected row in UITableView after reloadData

开发者 https://www.devze.com 2022-12-13 03:07 出处:网络
I write custom jabber client in iphone. I use xmppframework as engine. And I have UITableViewController with NSMutableArray for repesent contact list.

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 开发者_StackOverflow中文版reloadData?

Thx.


The easy way is something like this:

NSIndexPath *ipath = [self.tableView indexPathForSelectedRow];
[self.tableView reloadData];
[self.tableView selectRowAtIndexPath:ipath animated:NO scrollPosition:UITableViewScrollPositionNone];


Adding a bit to Marco's answer:

First, if you're using multiple selection, you can add this method to your ViewController and call it whenever you need to call [tableView reloadData]:

- (void)reloadTableView
{
    NSArray *indexPaths = [self.tableView indexPathsForSelectedRows];
    [self.tableView reloadData];
    for (NSIndexPath *path in indexPaths) {
        [self.tableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionNone];
    }
}

Second, if you're in a UITableViewController and you want to preserve selection after tableView appears there's a feature in UITableViewController: clearsSelectionOnViewWillAppear you can turn on or off.

See here: http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableViewController_Class/Reference/Reference.html


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)
    }
}


The workaround is to use reloadSections: instead of reloadData. For some reason reloadData removes the current selection.


Adding a delay didn't work for me (tested on iOS 8.4 and iOS 9). What did work was adding a call to -layoutIfNeeded on the selected cell, after calling -selectRowAtIndexPath:animated:scrollPosition:.

NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow];
[self.tableView reloadData];
[self.tableView selectRowAtIndexPath:selectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[[self.tableView cellForRowAtIndexPath:selectedIndexPath] layoutIfNeeded];


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)


This works for me:

NSIndexPath *indexPath = [table indexPathForSelectedRow];
[table reloadData];
double delayInSeconds = 0.01;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self.table selectRowAtIndexPath:indexPath animated:YES
                              scrollPosition:UITableViewScrollPositionNone];
});

The trick is to make selectRowAtIndexpath run later a bit. The code above is an Xcode template you can select when you start writing dispatch_after.


This is a solution for the case if you want to do table updates and keep the selection:

    NSIndexPath* pathToSelect = [self.tableView indexPathForSelectedRow];
    if (pathToSelect && newRows) {
        int row = pathToSelect.row;
        for (NSIndexPath* path in newRows) {
            if (path.section == pathToSelect.section && path.row <= pathToSelect.row) {
                row++;
            }
        }
        pathToSelect = [NSIndexPath indexPathForRow:row inSection:pathToSelect.section];
    }

    [self.tableView beginUpdates];
    if (reloadRows) [self.tableView reloadRowsAtIndexPaths:reloadRows withRowAnimation:UITableViewRowAnimationFade];
    if (newSections) [self.tableView insertSections:newSections withRowAnimation:UITableViewRowAnimationFade];
    if (newRows) [self.tableView insertRowsAtIndexPaths:newRows withRowAnimation:UITableViewRowAnimationFade];
    [self.tableView endUpdates];

    if (pathToSelect) {
        [self.tableView selectRowAtIndexPath:pathToSelect animated:NO scrollPosition:UITableViewScrollPositionNone];
    }


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)
        }
    }


SWIFT 3:

self.collectionView.reloadData()
self.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])


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
}


It sounds like you are not using a 'model' for the data - rather simply updating the 'view' (user interface), and thus is probably a bad design.

reloadData should cause the view to be updated with data from the model, which should contain the most current data to be displayed.

search for resources on the 'model view controller pattern'

0

精彩评论

暂无评论...
验证码 换一张
取 消