I have a UITableView of custom cells. Each cell has an NSTimer that can be set by the user and started by tapping a button. The timers work except when a new cell is added to the TableView. In the unwind method (coming from creating a new object for a new cell) I have to reload the data in the TableView so that the new cell appears. This however reloads all the cells causing any running timer to restart. Is there a way I can add the cell without restarting the running timers?
EDIT:
I have a ViewController that creates a new object and then back in the FirstViewController.m I have an unwind method that should update the tableView with a row for the new object. Using the reloadData method won't work because it reloads all of the cells. I would like to just add a new cell without reloading the entire tableView.
-(IBAction)unwind:(UIStoryboardSegue *)segue {
ABCAddItemViewController *source = [segue sourceViewController];
ABCItem *item = source.object;
if (item != nil) {
[self.objectArray addObject:item];
[self.tableView reloadData];
}
}
To do this I've tried adding these lines instead of [self.tableView reloadData]:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[self.objectArray indexOfObject:item] inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
However, the app crashes and I get this message:
'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
The number of rows in the tableView is determined by [self.objectArray count] in the numberOfRowsInSection: method.
It is not a good idea to add timers to cells because they are reused. They are views. My recommendation is to create some objects which have the text / info you want to display in these UITableViewCells and add the timers to those objects. This way the timer info is not stored in the cells and they will store state
There is no need to reload entire table for just inserting one cell. You can add the cell like
self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:row inSection:section]] withRowAnimation:UITableViewRowAnimationLeft];
[self.tableView endUpdates];
Related
I have UITableView with variable height custom cells and multiple sections which are not fixed, i am trying to implement load more functionality while user reach at first cell.
After fetching data i am arranging records into NSMutableArray which contains multi-dimensional array to store data section vice.
My problem is when i load more data i don't have idea about how many sections and how many rows in each section comes. So i can not add fix values to move my UITableView at particular position using methods like scrollToRowAtIndexPath or scrollRectToVisible
So every time after getting new record i called reloadData to update my number Of Sections and number Of Rows In Each Section, which also move control to first row of UITableView. I want to be present at current viewing cell not at first cell.
I have also tried answers at reloadData() of UITableView with Dynamic cell heights causes jumpy scrolling this question but that are not helping me.
Don't use reloadData if you want to stay at the same position. Use reloadRowsAtIndexPaths or insertRowsAtIndexPaths or reloadSections instead.
To refresh modified rows with animation:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[indexPathOfYourModifiedCell] withRowAnimation: UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
To add rows with animation (number of rows is automatically increased):
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPathOfYourNewCell] withRowAnimation: UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
Without animation (untested):
[UIView performWithoutAnimation:^{
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPathOfYourNewCell] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}];
Apple documentation: description here
I have a custom tableview cell and am getting data from the network 10 at a time. As users scroll down it fetches 10 more and displays the content.
Sometimes, my fetch can be a bit slow (>3-4 secs) and during that instance(when a fetch is currently happening), if my custom tableview cell is clicked (on the visible rows) then the app crashes at the following line. (I followed the example here to expand/collapse my cell and show more content)
Obviously the tableview:reloadRowsAtIndexPaths is called as and when network fetch is done and making changes to tableview data..so how can i fix this issue ?
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"ROW SELECTED..%ld",indexPath.row);
if (self.selectedIndex ==indexPath.row)
{
self.selectedIndex=-1;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
return;
}
if (self.selectedIndex!=-1){
NSIndexPath *prevPath = [NSIndexPath indexPathForRow:self.selectedIndex inSection:0];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:prevPath] withRowAnimation:UITableViewRowAnimationFade];
}
self.selectedIndex=(int)indexPath.row;
//[self.tableView beginUpdates];
//app crashes at this line !!
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
//[self.tableView endUpdates];
}
The exception i am getting is
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (10) must be equal to the number of rows contained in that section before the update (11), plus or minus the number of rows inserted or deleted from that section (1 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
If self.selectedIndex!=-1 is true, then you are going to ask the tableview to reload twice. I have a few suggestions:
put all those reload calls into dispatch blocks, so you don't issue them directly in a delegate method
aggregate the last two reloads into a single reload by using a mutable array, but again, dispatch to the main queue.
Also, add some logs. What happens if prevPath is out of range - that cell isn't valid any more?
EDIT: your answer is here:
"The number of rows contained in an existing section after the update (10) must be equal to the number of rows contained in that section before the update (11), plus or minus the number of rows inserted or deleted from that section (1 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out)."
Before the update you had 10 items in this section.
After the update you had 11 items in this section
you inserted one and deleted one (this is not shown in your code snippet)
you didn't move any in or out
So you have one more item in your section (told to the tableview in the delegate method) than insertions (net 0) and moves (net 0).
Here is my program. I want to create a simple list of items that display a number. When the rows are tapped the number will increment by one.
EDIT: Is it proper to change the UI of a row in the didSelectRowAtIndexPath function?
I created a UIViewController in Xcode 5 through a storyboard and it does everything right except I can't seem to stop the [tableView reloadData] from deselecting my row after being tapped. Specifically, I want the row to turn gray and then fade out normally.
I have tried selecting the row and then deselecting the row programatically after calling [tableView reloadData], but it doesn't work.
I know that if I was using UITableViewController that I could just call [self setClearsSelectionOnViewWillAppear:NO], but I'm not.
Is there a similar property I can set for UIViewController?
Here is the code:
[tableView beginUpdates];
[counts replaceObjectAtIndex: row withObject: [NSNumber numberWithInt:newCount]];
[tableView reloadData];
[tableView endUpdates];
I feel I may not be describing what is going on. I have a row that uses UITableViewCellStyle2, which displays a label to the left and right. On the right aligned text is a number that increments each time the row is tapped. Simply updating the data structure does not solve the problem. I need to update it visually. I don't need or want to replace the row, unless I have too. I just want to update the right-aligned text field AND keep the row from being deselected immediately without animation. I can do one or the other, but not both.
Is there a way to just update the right-aligned text field while still staying true to the MVC model?
Remove the [tableView reloadData]; from the code. It should not be called in the methods that insert or delete rows, especially within an animation block implemented with calls to beginUpdates and endUpdates .
Call reloadData method to reload all the data that is used to construct the table, including cells, section headers and footers, index arrays, and so on. For efficiency, the table view redisplays only those rows that are visible. It adjusts offsets if the table shrinks as a result of the reload. The table view's delegate or data source calls this method when it wants the table view to completely reload its data.
[tableView beginUpdates];
[counts replaceObjectAtIndex: row withObject: [NSNumber numberWithInt:newCount]];
[tableView endUpdates];
See the developer.apple section - reloadData
If you want to keep the selection after reload, the easy way is
NSIndexPath *selectedRowIndexPath = [tableView indexPathForSelectedRow];
[tableView reloadData];
[tableView selectRowAtIndexPath:selectedRowIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
Something weird is going on with my tableview when changing data. I have something like this:
// fadeout existing data
... // change data (new values for the cells are blank)
for{ // loop all rows
[TableView reloadRowsAtIndexPaths: withRowAnimation:]; // smooth fade out
}
// add one new row to the table
... // change data (just add one new row but leave the cells empty)
[TableView reloadData] // reload all of the data (new row should be added)
... // change data (just add values to the existing cells)
for{ // loop all rows
[TableView reloadRowsAtIndexPaths: withRowAnimation:]; // smooth fade in
}
In theory, at least what I think, this should work. But I had some glitches so I added NSLogs in the for loops and in the: - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath so I noticed that my [tableView reloadData] executes after the first row of fade in loop!##
I was thinking about making some kind of a delay between reloadData and fade in loop, but I don't want to force it to work, I want it to work properly.
Is there a better way to accomplish what I'm trying to do?
Can I dynamically add one row at the bottom of the table without calling the 'reloadData' method?
Thanks.
Yes, just use the tableView insertRowsAtIndexPaths method, which will then call your dataSource to load those new rows.
To load a single row at the end of your table, you'd first update your data source to include the new row, then generate the indexpath using:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[yourdata count]-1 inSection:0];
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[tableView insertRowsAtIndexpaths:indexPaths withRowAnimation:whateveryouwant];
Remember, that will immediately call your tableView:cellForRowAtIndexPath method, so update your dataSource to include the extra row before inserting the new row into your table, and be careful that the row index in the indexPath you specify matches the newly added item in your data source.
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.