RowAnimation looks weird with different height cells - iphone

I have a problem with animating deletes and inserts on a UITableView. The Fact ist, that I have different cell heights, some 44.0 some 135.0 now. I have uitableviewstylegrouped with different sections. The first row of each sections is a grouping row. On click I remove all rows of this section except the grouping row. But that animation looks weird, when animating (UITableViewRowAnimationTop) the 135px height cell. I tried to set self.tableview.cellheight to 135 and commented out the tableView:cellHeightForIndexPath-Method. And the Animation works fine. Or I set every cellheight to 135 in tableView:cellHeightForIndexPath-Method.
It looks like the animation process checks the height of the first row in the sections an takes that height of the cell for all following cells to animate.
Somebody has an idea?
- (void) showhideGroup:(int)group
{
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
MCGroup *handleGroup = [groups objectAtIndex:group];
NSMutableArray *groupRows = [visibleGroupData objectForKey:handleGroup.title];
[self.tableView beginUpdates];
if (!handleGroup.hidden) {
int row = 0;
for(MobileFieldMapping *field in groupRows)
{
if (![field.datatype matchesInsensitive:GROUPING_CELL])
{
NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:group];
[indexPaths addObject:path];
}
row++;
}
row = 0;
for(NSIndexPath *index in indexPaths)
{
[groupRows removeObjectAtIndex:index.row-row];
row++;
}
[self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
handleGroup.hidden=YES;
}
else
{
NSMutableArray *allGroupRows = [groupData objectForKey:handleGroup.title];
int row = 0;
for (MobileFieldMapping *field in allGroupRows)
{
if (![groupRows containsObject:field])
{
NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:group];
[indexPaths addObject:path];
[groupRows insertObject:field atIndex:row];
}
row++;
}
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
handleGroup.hidden=NO;
}
[self.tableView endUpdates];
[indexPaths release];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
if ([cell isKindOfClass:[FormCell class]])
{
return [(FormCell*)cell height];
}
return 44.0;
}

I had the same problem.
My workaround is to use fade section animation:
[tableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade];
Instead of one for elements:
[tableView deleteRowsAtIndexPaths:tmpArray withRowAnimation:UITableViewRowAnimationTop];
This looks much smoother.

An old one that is still unanswered. I assume you figured it out a long time ago, but here is my first thought. (I recall trying to do something like this myself.)
When the UITableView calls heightForRowAtIndexPath:, it will be because the table is trying to prepare the cell, so you can't use the cell to return the height. This may cause an infinite regression in calls or it may detect this and just give up or throw an exception and leave you holding the bag.
You must calculate the cell height without using the cell itself.
As for the cell height assumption, try calling insert/delete for individual cells rather than a single call to do it all at once. The UITableView will still batch them up for the animation after you call endUpdates.

Related

Unnatural jerk with UITableView whe cell height is changed dynamically

Here is what I want in my app. Shown below are two screenshots of the iPhone app Store:
I basically need a "Read More" feature just like it is used in the app store (See the "Description" section in the two images above). I am assuming that each section here (Description, What's New, Information etc.) is a table view cell. And the text inside is a UILabel or UITextField.
Here is what I have tried so far to add this feature:
NSString *initialText = #"Something which is not a complete text and created just as an example to...";
NSString *finalText = #"Something which is not a complete text and created just as an example to illustrate my problem here with tableviews and cel heights. bla bla bla";
NSInteger isReadMoreTapped = 0;
My cellForRowAtIndexPath function:
// Other cell initialisations
if(isReadMoreTapped==0)
cell.label.text = initialText;
else
cell.label.text = finalText;
return cell;
My heightForRowAtIndexPath function:
// Other cell heights determined dynamically
if(isReadMoreTapped==0){
cell.label.text = initialText;
cellHeight = //Some cell height x which is determined dynamically based on the font, width etc. of the label text
}
else{
cell.label.text = finalText;
cellHeight = //Some height greater than x determined dynamically
}
return cellHeight;
Finally my IBAction readMoreTapped method which is called when the More button is tapped:
isReadMoreTapped = 1;
[self.tableView beginUpdates];
[self.tableView endUpdates];
NSIndexPath* rowToReload = [NSIndexPath indexPathForRow:2 inSection:0]; // I need to reload only the third row, so not reloading the entire table but only the required one
NSArray* rowsToReload = [NSArray arrayWithObjects:rowToReload, nil];
[self.tableView reloadRowsAtIndexPaths:rowsToReload withRowAnimation:UITableViewRowAnimationNone];
After doing all this, I do get the required functionality. The new height of that particular cell is calculated and the new text loaded into it. But there is a very unnatural jerk on the TableView which results in a bad User experience. That is not the case with the app store More button though. There is no unnatural jerk in its case. The TableView remains at its place, only the changed cell has its size increased with the text appearing smoothly.
How can I achieve the smoothness as done in the iPhone app store More button?
Your problem might come from reloading the row. You want to try to configure the cell properties directly. I usually use a dedicated method to configure my cell content so I don't have to reload rows.
- (void)configureCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if(isReadMoreTapped==0)
cell.label.text = initialText;
else
cell.label.text = finalText;
// all other cell configuration goes here
}
this method is called from the cellForRowAtIndexPath method and it will configure the cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[self configureCell:cell forRowAtIndexPath:indexPath];
return cell;
}
and you would call this method directly to avoid reloading:
isReadMoreTapped = 1;
[self.tableView beginUpdates];
[self.tableView endUpdates];
NSIndexPath* rowToReload = [NSIndexPath indexPathForRow:2 inSection:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:rowToReload];
[self configureCell:cell forRowAtIndexPath:rowToReload];
Please try the following changes to your code, I think it will fix your problem.
no need to set cell.label.text in heightForRowAtIndexPath; Please remove them.
in the readMoreTapped, update table is enough:
isReadMoreTapped = 1;
[self.tableView beginUpdates];
[self.tableView endUpdates];
Either remove the calls to:
[self.tableView beginUpdates];
[self.tableView endUpdates];
Or change to ensure that your reloading code is between them. I would remove them as a single row reload is handled well with the method you use:
[self.tableView reloadRowsAtIndexPaths:rowsToReload withRowAnimation:UITableViewRowAnimationNone];
You just need to specify a row animation like fade.
Okay, I finally solved the problem with the help of Matthias's answer (the accepted answer) and my own optimisations. One thing that definitely should be done is to create a dedicated function like configureCell: forRowAtIndexPath: to directly configure cell properties (see Mathias's answer). Everything remains the same with Matthias's answer except:
Before, I was calculating the heights of each cell everytime the heightForRowAtIndexPath function was called without caching(saving) them anywhere and hence when [self.tableView beginUpdates]; and [self.tableView endUpdates]; were called each cell height was calculated again. Instead, what you have to do is to save these cell heights in an array/dictionary so that whenever the More button is tapped, you calculate the height of only the cell that was changed. For the other cells, just query the array/dictionary and return the saved cell height. This should solve any problems with the tableView scroll after the cell height update. If anyone else still face a similar issue as this, please comment on this post. I would be happy to help

Strange animation when moving last row out of section and deleting section

I have a multi-section tableview. In edit mode I allow rows to be moved from one section to another. Once the final row is removed from one section I delete that section. So I am using deleteSection inside moveRowAtIndexPath.
When the final item is moved from the section, the section header disappears as planned. But there is a very strange animation bug, where the moved row seems to 'merge' with the row it is dropped above, and an empty row is displayed at the bottom of the 'to' section (probably because the numberOfRows for that section is correct, but 2 rows are in the same position). Even stranger, when I click the reorder control for this row (not moving the item, simply touching and releasing), the two items 'unmerge'.
I have posted a video demonstrating this.
I have tried wrapping my data changes and view changes in begin/end updates, but to no avail.
I have uploaded a test project here, and I will also post the code below. A couple of points:
I have tried to replicate my data source's format in the demo project, in case this is where the problem originates. The key thing is that my source is a composite array of two other arrays (though I can't see why this would be an issue).
To see the behavior in question, move the two rows in the bottom section, up into the top section. Don't drop them in the last row on the top section though, since this seems to work ok.
Moving rows the other way, from the top section to the bottom section, is buggy in this demo project.
Code (all of this is in the demo project):
I set up my arrays in loadView:
- (void)loadView{
array1 = [NSMutableArray array];
[array1 addObject:#"test 0"];
[array1 addObject:#"test 1"];
[array1 addObject:#"test 2"];
array2 = [NSMutableArray array];
[array2 addObject:#"test a"];
[array2 addObject:#"test b"];
[super loadView];
}
I also have a method that returns a combination of these arrays - this is used as the data source:
- (NSMutableArray *)sourceArray{
NSMutableArray *result = [NSMutableArray array];
if (array1.count > 0) {
[result addObject:array1];
}
if (array2.count >0) {
[result addObject:array2];
}
return result;
}
Which allows for very simple number of rows/sections:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return self.sourceArray.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [[self.sourceArray objectAtIndex:section] count];
}
Standard Cell/Header formatting:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.textLabel.text = [[self.sourceArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [NSString stringWithFormat:#"Section %i", section];
}
This is where I do the magic
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
NSMutableArray *fromArray = [self.sourceArray objectAtIndex:fromIndexPath.section];
NSMutableArray *toArray = [self.sourceArray objectAtIndex:toIndexPath.section];
NSString *movedObject = [[self.sourceArray objectAtIndex:fromIndexPath.section] objectAtIndex:fromIndexPath.row];
[fromArray removeObject:movedObject];
[toArray insertObject:movedObject atIndex:toIndexPath.row];
if ([self.tableView numberOfRowsInSection: fromIndexPath.section] == 0) {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:fromIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
}
}
I notice that the row that comes from the to-be-deleted section is the one that disappears until you retouch the order control.
I suspect that when this datasource method is called by the tableview, its state is still in the middle of performing the move, so calling 'deleteSections' will make the table try and delete the row you're moving. It's not so much of a merge as the fact that it's fading away at the same rate as the section header, and the one below it is just scooting back up to fill the space.
Tapping the control causes the table view to rejigger itself and realize that the row isn't actually gone.
to try and work around this, try running the deletion in the next runloop, via a dispatch call, like:
if ([self.tableView numberOfRowsInSection: fromIndexPath.section] == 0) {
dispatch_async(dispatch_get_main_queue(), ^() {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:fromIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
});
}
this will cause the deletion to run on the main thread still, but allow the 'moveRow' and whatever call stack it happens to be in finish up its logic before the deletion call
Your problem is in the animation. One is being done while another is not yet finished (moving & deleting animation) causing one cell to be drawn upon the other. You can verify this by moving the cells around again. The correct order will then be displayed. According to Apple's docs on the UITableView:
Note: The data source should not call setEditing:animated: from within its implementation of tableView:commitEditingStyle:forRowAtIndexPath:. If for some reason it must, it should invoke it after a delay by using the performSelector:withObject:afterDelay: method.
Therefore to fix this, do this to your code:
if ([self.tableView numberOfRowsInSection: fromIndexPath.section] == 0) {
[self performSelector:#selector(someMethod:) withObject:fromIndexPath afterDelay:1.0];
}
- (void) someMethod:(NSIndexPath *) fromIndexPath {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:fromIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
}
Should work fine. Just change the delay to something shorter that suites you.
On the off chance that your rows or what's inside them can take focus, have you checked that you have called resignFirstResponder or [view endEditing:YES]? We saw this when we used text fields and (IIRC it was iOS 4 version dependent too) left the focus in one of the fields.
You have to reload the tableview after deleting the section. Try this code.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
NSMutableArray *fromArray = [self.sourceArray objectAtIndex:fromIndexPath.section];
NSMutableArray *toArray = [self.sourceArray objectAtIndex:toIndexPath.section];
NSString *movedObject = [[self.sourceArray objectAtIndex:fromIndexPath.section] objectAtIndex:fromIndexPath.row];
[fromArray removeObject:movedObject];
[toArray insertObject:movedObject atIndex:toIndexPath.row];
if ([self.tableView numberOfRowsInSection: fromIndexPath.section] == 0) {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:fromIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView reloadData];
}
}
Swap the order of fromArray and toArray in your code. If the item has a retain count of 1 prior to removing it from the array, it will have a retain count of 0 before adding it to toArray.
If you swap the order, the item will go from retain count of 1 to 2 then back to 1 when the remove is complete.
I think the UITableViewRowAnimationFade animation is interfering with the UITableViewCell move animation. One thing you can try is to delay the section deletion a little bit late in order for the cell move row animation to finish.
Try replace your code with the following code.
-(void)deleteSection:(NSIndexSet*)indexSet
{
[self.tableView deleteSections:indexSet withRowAnimation:UITableViewRowAnimationFade];
}
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
NSMutableArray *fromArray = [self.sourceArray objectAtIndex:fromIndexPath.section];
NSMutableArray *toArray = [self.sourceArray objectAtIndex:toIndexPath.section];
NSString *movedObject = [[self.sourceArray objectAtIndex:fromIndexPath.section] objectAtIndex:fromIndexPath.row];
[fromArray removeObject:movedObject];
[toArray insertObject:movedObject atIndex:toIndexPath.row];
if ([self.tableView numberOfRowsInSection: fromIndexPath.section] == 0) {
[self performSelector:#selector(deleteSection:) withObject:[NSIndexSet indexSetWithIndex:fromIndexPath.section] afterDelay:1.0];
// [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:fromIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
}
}
a solution that lost animation on last row :
if([listOfItemsOnTransaction count]==indexPath.row){
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView reloadData];
}else
{
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil]
withRowAnimation:UITableViewRowAnimationFade];
}

UITableView discloure indicator going nuts

I have a UITableView (on a UIViewController) which is pushed via a navigationController. Along with pushing, I select with which array i want to populate the table. The code for pushing is like this:
if(self.newView == nil)
{
NewView *viewTwo = [[NewView alloc] initWithNibName:#"Bundle" bundle:[NSBundle mainBundle]];
self.newView = viewTwo;
[viewTwo release];
}
[self.navigationController pushViewController:self.newView animated:YES];
newView.tableArray=newView.arrayWithOptionOne;
[newView.tableView reloadData];
All works well and the table gets reloaded every time. However in the last row of section 0, there is a switch which loads section 1.
The last row of section 1 is tappable (didSelect…) and it loads a modalView. On this last rod I added a disclosure indicator and also the blue background when tapping. The table has sliders, labels, etc. So the customization is quite long:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = nil;
static NSString *kDisplayCell_ID = #"DisplayCellID";
cell = [tableView dequeueReusableCellWithIdentifier:kDisplayCell_ID];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:kDisplayCell_ID] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
else
{
UIView *viewToRemove = nil;
viewToRemove = [cell.contentView viewWithTag:1];
if (viewToRemove)
[viewToRemove removeFromSuperview];
UIView *viewToRemove2 = nil;
viewToRemove2 = [cell.contentView viewWithTag:2];
if (viewToRemove2)
[viewToRemove2 removeFromSuperview];
}
if (indexPath.section==0) {
UIControl *cellValueS = [[[arrayTable objectAtIndex:indexPath.section] objectForKey:kViewKey] objectAtIndex:indexPath.row];
[cell.contentView addSubview:cellValueS];
}
if (indexPath.section==0 && indexPath.row==3) {
UIControl *cellValueL = [[[arrayTable objectAtIndex:indexPath.section] objectForKey:kViewLabel] objectAtIndex:0] ;
[cell.contentView addSubview:cellValueL];
}
if (indexPath.section==1 && indexPath.row==0){
UIControl *cellValueS = [[[arrayTable objectAtIndex:indexPath.section] objectForKey:kViewKey] objectAtIndex:indexPath.row] ;
[cell.contentView addSubview:cellValueS];
}
if (indexPath.section==1 && indexPath.row==1) {
UIControl *cellValueS = [[[arrayTable objectAtIndex:indexPath.section] objectForKey:kViewKey] objectAtIndex:indexPath.row] ;
[cell.contentView addSubview:cellValueS];
UIControl *cellValueL = [[[arrayTable objectAtIndex:indexPath.section] objectForKey:kViewLabel] objectAtIndex:0] ;
[cell.contentView addSubview:cellValueL];
}
if (indexPath.section==1 && indexPath.row==2) {
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
}
return cell;
}
So far also works ok.
The problem is that when I go back to the previous view and select another option to populate the table, when it's pushed again, I see the disclosure indicator and blue selection background on other rows on the same section. I've observed that it depends on where the table is scrolled.
I've tried to understand why does it happen, but i can't. I've somehow solved the problem by setting newView to nil and releasing it and then allocating it again before it gets pushed again.
Am I doing something wrong here? or why is the disclosure indicator and tapping background appearing where they are not supposed to be?
Thanks in advance!
action of the switch
-(void)extraOptionsSwitchAction:(id)sender{
switch(extraOptionsSwitch.isOn) {
case 1:
// [self.tableView beginUpdates];
[self.tableView insertSections: [NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
//[self.tableView reloadData];
// [self.tableView endUpdates];
break;
case !1:
// [self.tableView beginUpdates];
[self.tableView deleteSections: [NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
// [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView reloadData];
// [self.tableView endUpdates];
break;
}
}
It has to to with reusing cells. You probably don't differentiate between the different cell types in your cell creation method - so one cell that went offscreen can easily be reused for another (different type) cell. Further, you seem to add subviews over and over again - only do that when you instantiate the cell (with alloc/int), and not when configuring.
Also, for different cell types, use different identifiers (you didn't show this code).
The programming guides have good example on table views and their cells and reuse pattern. It's worth reading a couple of times - it's easy to get wrong and is a main topic for performance tuning.
Edit
Now that you added more code, another problem seems to be here:
if (indexPath.section==1 && indexPath.row==2) {
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
}
You are missing an else part. The selectionStyle and accessoryType are set to what they were set to before - you miss to configure the cells correctly for all other cells than that special one.
Each cell type should really get its own identifier though. If the adding/removing of subviews work as expected is hard to tell from that code.
One thought: As you aren't really reusing a lot of the cells here anyhow you could even disable the reuse by changing
static NSString *kDisplayCell_ID = #"DisplayCellID";
cell = [tableView dequeueReusableCellWithIdentifier:kDisplayCell_ID];
to
static NSString *kDisplayCell_ID = nil;
This would just always produce a new cell. I wouldn't recommend this in the general case, though.
This is due to cellReusability, and it has, for a long time, wasted so many developers' time and effort. If you knew the concept of cell reusability, you would know that while scrolling, the index of the row and sections remain the same (although you expect it to be different for a different position on the uiTableView).
The only way is to subClass the UITableViewCell and create your own CustomUITableViewCell, and implement that with the disclosure indicator, or resist your input to just a small TableView that fits the screen and make scrollable = NO.

insertRowsAtIndexPaths Doesnot call cellForRowAtIndexPath

I created sample tableview application and I have an add button above the tableview, when user pressed the add button only we want to add row to table view.
I am write code like this
- (void)viewDidLoad {
isEditing = NO;
Mutarray = [[NSMutableArray alloc]init];
[super viewDidLoad];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
if (isEditing)
return [Mutarray count];
else
return 0;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
}
[TableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
NSLog(#"Row = %d", indexPath.row);
// Configure the cell...
cell.textLabel.text = [Mutarray objectAtIndex:indexPath.row];
return cell;
}
//When add button pressed
-(IBAction)Add:(id)sender
{
isEditing = YES;
[Mutarray addObject:[NSString stringWithFormat:#"%d",[Mutarray count]]];
NSArray *insertIndexPaths = [NSArray arrayWithObjects:
[NSIndexPath indexPathForRow:[Mutarray count]-1 inSection:0],
nil];
[self.TableView beginUpdates];
[self.TableView insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationRight];
[self.TableView endUpdates];
}
This code works fine.But problem is my tableview height is 418 , it shows only 10 row as visible. So when the 11 row was added it added in tableview but not calling this cellForRowAtIndexPath function so I'm not able to auto scroll the page... The first 10 row it calls the cellForRowAtIndexPath function.
So what I my doubts is why the cellForRowAtIndexPath function only calls visible rows?
Then how can I auto scroll my tableview?
So what I my doubts is why the
cellForRowAtIndexPath function only
calls visible rows ?
It is so for optimization reasons. If table view had created cells for all its rows it could downgrade performance dramatically. Instead of that table view creates cells only for rows that are visible (and probably a couple more to ensure smooth scrolling) and then reuses them for rows that become visible - so actually you can show hundreds of row with just, say, 10 cell objects - it is a huge save.
Then how can I auto scroll my
tableview ?
You can scroll right after you added a row in your add method:
...
[table endUpdates];
[table scrollToRowAtIndexPath:[insertIndexPaths objectAtIndex:0]
atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
P.S. conventionally method and variable names in objective-c start with lowercase, it is better style to follow that guideline.

Inserting and deleting UITableViewCell at the same time not working

I'm having quite a bit of pain inserting and deleting UITableViewCells from the same UITableView!
I don't normally post code, but I thought this was the best way of showing where I'm having the problem:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 5;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (iSelectedSection == section) return 5;
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//NSLog(#"drawing row:%d section:%d", [indexPath row], [indexPath section]);
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
if (iSelectedSection == [indexPath section]) {
cell.textColor = [UIColor redColor];
} else {
cell.textColor = [UIColor blackColor];
}
cell.text = [NSString stringWithFormat:#"Section: %d Row: %d", [indexPath section], [indexPath row]];
// Set up the cell
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic -- create and push a new view controller
if ([indexPath row] == 0) {
NSMutableArray *rowsToRemove = [NSMutableArray array];
NSMutableArray *rowsToAdd = [NSMutableArray array];
for(int i=0; i<5; i++) {
//NSLog(#"Adding row:%d section:%d ", i, [indexPath section]);
//NSLog(#"Removing row:%d section:%d ", i, iSelectedSection);
[rowsToAdd addObject:[NSIndexPath indexPathForRow:i inSection:[indexPath section]]];
[rowsToRemove addObject:[NSIndexPath indexPathForRow:i inSection:iSelectedSection]];
}
iSelectedSection = [indexPath section];
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:rowsToRemove withRowAnimation:YES];
[tableView insertRowsAtIndexPaths:rowsToAdd withRowAnimation:YES];
[tableView endUpdates];
}
}
This code creates 5 sections, the 1st (indexed from 0) with 5 rows. When you select a section - it removes the rows from the section you had previously selected and adds rows to the section you just selected.
Pictorally, when I load up the app, I have something like this:
http://www.freeimagehosting.net/uploads/1b9f2d57e7.png http://www.freeimagehosting.net/uploads/1b9f2d57e7.png
Image here: http://www.freeimagehosting.net/uploads/1b9f2d57e7.png
After selecting a table row 0 of section 2, I then delete the rows of section 1 (which is selected by default) and add the rows of section 2. But I get this:
http://www.freeimagehosting.net/uploads/6d5d904e84.png http://www.freeimagehosting.net/uploads/6d5d904e84.png
Image here: http://www.freeimagehosting.net/uploads/6d5d904e84.png
...which isn't what I expect to happen! It seems like the first row of section 2 somehow remains - even though it definitly gets deleted.
If I just do a [tableView reloadData], everything appears as normal... but I obviously forefit the nice animations.
I'd really appreciate it if someone could shine some light here! It's driving me a little crazy!
Thanks again,
Nick.
Struggled to get this to work. Here's my code to add a row to my tableView:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[tableView beginUpdates];
[dataSource insertObject:[artistField text] atIndex:0];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[tableView endUpdates];
I seem to remember that numberOfRowsInSection: will get called when you call deleteRows or insertRow, you need to be really careful that the reality numberOfRowsInSection cliams matches your changes. In this case you may want to try moving the iSelectedSection = [indexPath section]; line to after the endUpdates.
I don't remember where I read this but I believe you shouldn't perform table row updates (insertions and deletions) from inside one of the table view delegate functions. I think a better alternative would be to do a performSelectorOnMainThread passing along the necessary information needed to perform the updates as an object. Something like:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// ....
[self performSelectorOnMainThread: #selector(insertRows:)
withObject: someObjectOrNil]; // double check args
}
- (void) insertRows: (NSObject*)someObjectOrNil {
[tableView beginUpdates];
// update logic
[tableView endUpdates];
// don't call reloadData here, but ensure that data returned from the
// table view delegate functions are in sync
}
In the code you posted, your loop index runs from 0 to 4, which suggests that it would delete all of the rows in section 1, and then add five new rows to section 2. Since each section already has a row 0, this would add a second instance of section 2, row 0 to the table.
I would suggest having your loop run from 1 to 4:
for (int i=1; i<5; i++)
{
// ...
}
FYI: This bug seems to have been fixed completely with the 2.2 iPhone update.
Thanks Apple!
Nick.