Reload a table view's data without clearing its selection state - ios

I have a table view with selectable rows.
When I reload the table view some new rows might be added (or removed) and some labels in the table view's cells might change. That's what I want to achieve by calling [tableView reloadData].
Unfortunately that method also clears the table view's whole state - including the selection. But I need to keep the selection.
So how can I reload all the data in a table view while still keeping the selected rows selected?

You can store the index path of the selected row with:
rowToSelect = [yourTableView indexPathForSelectedRow];
Before reload the data. And after reload use:
[yourTableView selectRowAtIndexPath:rowToSelect animated:YES scrollPosition:UITableViewScrollPositionNone];

JeroVallis solution works for single selection table views.
Based on his idea this is how I made it work with multiple selection:
NSArray *selectedIndexPaths = [self.tableView indexPathsForSelectedRows];
[tableView reloadData];
for (int i = 0; i < [selectedIndexPaths count]; i++) {
[tableView selectRowAtIndexPath:selectedIndexPaths[i] animated:NO scrollPosition:UITableViewScrollPositionNone];
}

The most efficient way is to keep the selected state in the data model
Add a boolean property isSelected in the struct or class which represents the data source.
In cellForRowAt set the selected state of the cell according to the property.
In didSelectRow toggle isSelected in the data source item and reload only the particular row at the given index path.

An alternative that has some advantages is to only reload the rows that have not been selected. Swift code below.
if var visibleRows = tableView.indexPathsForVisibleRows,
let indexPathIndex = visibleRows.index(of: indexPath) {
visibleRows.remove(at: indexPathIndex)
tableView.reloadRows(at: visibleRows, with: .none)
}

Swift 4.2 Tested
The correct way to update selected rows after reload table view is:
// Saves selected rows
let selectredRows = tableView.indexPathsForSelectedRows
tableView.reloadData()
// Select row after table view finished reload data on the main thread
DispatchQueue.main.async {
selectredRows?.forEach({ (selectedRow) in
tableView.selectRow(at: selectedRow, animated: false, scrollPosition: .none)
})
}

Related

Maintain UITableView cell position with dynamic height cells after reloading source data iOS

In chat application, I am implementing Load More button on top of chat table view.
When user taps load more button, older messages (plus all the current messages which are present on screen) get fetched from DB (using NSFetchedResultsController) and then I am calling reloadData() method on chat table view.
I want to set the scroll position of tableview to the row which was top row before reloading table.
All the message cells are dynamic in height according to the message text.
I am using auto layout on storyboard.
My code:
let oldContentheight = _chatTableView.contentSize.height
_chatTableView.reloadData()
executeOnMain {
if self.firstTimeFetching {
self.firstTimeFetching = false
self.scrollTableToLastRow(withAnimation: false)
//this is for going to last cell when we come to chat screen
}
else {
//after pressing load more button
let newContentHeight = self._chatTableView.contentSize.height
let newOffset = CGPointMake(0, newContentHeight - oldContentheight)
self._chatTableView.setContentOffset(newOffset, animated: false)
}
}
First, declare property to save the indexpath of top visible cell
#property(strong,nonatomic) *topVisibleIndexPath;
Then when you click the Load More button, you get the top visible cell IndexPath and save
NSArray *visibelIndexpaths = [yourTableView.indexPathsForVisibleRows];
if(visibelIndexpaths.count > 0)
self.topVisibleIndexPath = [visibelIndexpaths firstObject];
After reload, you call method :
[yourTableView scrollToRowAtIndexPath:self.topVisibleIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]
Instead of using reloadData, consider doing batch updates. reloadData will load up new elements from your dataSource, but it will also have a side effect of resetting your scrollView's scroll position to the top.
What you want is to use this pattern:
tableView.beginUpdates()
tableView.insertRowsAtIndexPaths([/* new rows here as an array of NSIndexPaths */], withRowAnimation: .Automatic)
tableView.endUpdates()
where the insertRowsAtIndexPaths contain the new rows you want to add.

Accessibility collectionView focus changes when reloaded, iOS

I'm working on the accessibility of a calendar which is actually a collectionView. Whenever a cell is tapped, the collectionView will be reloaded by calling
[self.collectionView reloadData];
The problem is if the voiceOver is running, the focus will move to another place after the cell tapped because that cell is reused on somewhere else.
Is there anyway to keep the focus where it was after the reloadData? Thanks!
Just find a workaround for this. The focus is changed because the focused cell is reused somewhere else when doing [colleciontView reloadData].
So if we reload the collectionViewCells one by one, that focused cell will not be used anywhere else. I call this method to reload the collectionView when VoiceOver is running.
- (void)reloadCalendarCollectionView {
NSInteger items = [self.calendarItems count];
for (NSInteger i = 0; i < items; i++) {
[self.collectionView reloadItemsAtIndexPaths:#[[NSIndexPath indexPathForItem:i inSection:1] ]];
}
}
You could try doing self.collectionView.accessibilityElementsHidden = YES before reloading data. Then, when it completes, you will have to do the inverse & then post a notification for the cell you're looking for.
We have a collection view and this collection view reloading data in every second. When user taps cell, focus changing after every reload, so collectionview cell selects wrong cell at indexPath.
Create a protocol for delegation in cell:
protocol AccessibilityCellProtocol{
func accessibilityFocused(cell:UICollectionViewCell)
}
Override accessibilityElementDidBecomeFocused in your cell:
override class func accessibilityElementDidBecomeFocused(){
self.delegate.accessibilityFocused(cell:self)
}
In view controller create an selectedIndexPath variable. Assign it in delegation method.
func accesibilityFocused(cell:UITableViewCell){
selectedIndex = collectionView.indexPath(for: cell)
}
And in your didSelectItemAtIndexPath method:
if UIAccessibility.isVoiceOverRunning{
cellTappedWith(indexPath:selectedIndex)
return
}
cellTappedWithIndexPath(indexPath:indexPath)

How to animate changing order in UITableView?

I have a table view with 10 items. I can click a button and their order changes.
Then I reload this with the following code (I have and will ever have only one section):
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic]
I tried different animations, but nothing works as I want.
I want the rows to slide into their new positions. How do I do this?
What I did:
[tableView beginUpdates]
Save all current cells to NSMutableArray. The model object is also stored in custom cell property.
Change data source.
Iterate over cells in array (index i), for each cell iterate over data source array (index j). If object in cell and in data source is the same, move cell from position i to position of object in data source array (j).
[tableView endUpdates]
You can animate the transitions with UITableView methods beginUpdates and endUpdates, in conjunction with moveRowAtIndexPath.
Here's a sample of sorting animation with asc/desc
table.beginUpdates()
for (index,_) in tableviewItems.enumerated(){
let newRow = tableviewItems.count - index - 1
table.moveRow(at: IndexPath(row: index, section: 0), to: IndexPath(row: newRow, section: 0))
}
tableviewItems.reverse()
table.endUpdates()

core data saving in textboxes in table view controller

I have created a five column (text boxes) cell (row) in table view controller with an option of add button. When a user clicks on add button, a new row (cell) with five column (text boxe) is added in a table view controller with null values. I want that when user fills the text boxes the data should get saved in database or if he changes any data in previous text boxes also it get saved.
this is my save btn coding..
- (IBAction)btn_save:(id)sender
{
NSInteger noOfRow=[(NSSet *)[projectObject valueForKey:#"rs_project_Input"] count];
NSLog(#"Save Btn: No of rows for saving %d",noOfRow);
for (row1=0; row1<noOfRow; row1++)
{
NSLog(#"Row is %d",row1);
path = [NSIndexPath indexPathForRow:row1 inSection:0];
Input_Details *inputDetailsObject1=[[self sortInputs] objectAtIndex:path.row];
/*
Update the input details from the values in the text fields.
*/
EditingTableViewCell *cell;
cell = (EditingTableViewCell *)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:row1 inSection:0]];
inputDetailsObject1.mh_Up= cell.cell_MH_up.text;
inputDetailsObject1.mh_down=cell.textField.text;
NSLog(#"Saving the MH_up value %# is saved in save at index path %d",inputDetailsObject1.mh_Up,row1);
[masterController saveContext];
}
[self.tableView reloadData];
}
My problem is that the code is not helping in saving the data. Plz help.
Thanks
You try to use the table view cells as a data source, which will not work (apart from being a bad design). The problem is that
[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:row1 inSection:0]]
returns nil if the cell at that row is currently not visible. (A table view allocates only cells for the visible rows and reuses these cells as you scroll up or down.)
You have to track changes to the text fields immediately, e.g. by implementing a textFieldDidEndEditing: delegate function, and store the changed text in your data source.

Table view cell indexing when using insertRowsAtIndexPaths

Ok, I'm stuck. This is an extension of a previous post of mine. Here is what I am trying to do.
I have an Edit button on a navigation bar that when pressed adds a cell at the beginning of my one section table view. The purpose of this cell if to allow the use to add new data to the table; thus it's editing style is Insert. The remaining cells in the table are configured with an editing style of Delete.
Here is my setediting method:
- (IBAction) setEditing:(BOOL)isEditing animated:(BOOL)isAnimated
{
[super setEditing:isEditing animated:isAnimated];
// We have to pass this to tableView to put it into editing mode.
[self.tableView setEditing:isEditing animated:isAnimated];
// When editing is begun, we are adding and "add..." cell at row 0.
// When editing is complete, we need to remove the "add..." cell at row 0.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
NSArray* path = [NSArray arrayWithObject:indexPath];
// fill paths of insertion rows here
[self.tableView beginUpdates];
if( isEditing )
[self.tableView insertRowsAtIndexPaths:path withRowAnimation:UITableViewRowAnimationBottom];
else
[self.tableView deleteRowsAtIndexPaths:path withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView endUpdates];
// We nee to reload the table so that the existing table items will be properly
// indexed with the addition/removal of the the "add..." cell
[self.tableView reloadData];
}
I am accounting for this extra cell in my code, except I now have two index paths = [0,0] - the new cell and the old original first cell in the table. If I add a call to reload the table view cell in setEditing, the cells are re-indexed, but now my table view is no longer animated.
I want my cake and eat it too. Is there another way to accomplish what I am trying to do and maintain animation?
--John
You can do what you want but you need to keep your data source consistent with the table. In other words, When the table is reloaded, tableView:cellForRowAtIndexPath and the other UITableViewDataSource and UITableViewDelegate methods responsible for building the table should return the same cells depending on editing state that you are adding/removing in setEditing:antimated:.
So, when you insert/delete a cell in setEditing:animated: you need to also make sure your data source reflects the same change. This can be tricky if you are adding a special cell to the beginning of a section but the rest of the data is from an array. One way to do this is while reloading the table, if editing, make row 0 the add cell and use row-1 for your array index for subsequent cells. If you do that you'd also need to add one to tableView:numberOfRowsInSection: to account for the extra cell.
Another way would be to have a section for the add cell and it would have 0 rows when not editing, 1 row otherwise and you return the appropriate cell. This will also require you to configure your table and cell(s) appropriate depending on how you want things to look.

Resources