How to use tableView:editingStyleForRowAtIndexPath? - iphone

I have a very simple application with a UITableViewController. Upon EDITING, I am trying to slide a row into position 0 of the first section. The new row should have an INSERT editing style while the existing row should have a DELETE style.
I've overridden the following 4 methods:
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.editing && section == 0) {
return2;
}
return 1;
}
- (UITableViewCellEditingStyle)tableView:(UITableView*)tableView
editingStyleForRowAtIndexPath:(NSIndexPath*)indexPath {
int section = indexPath.section;
int row = indexPath.row;
if (self.editing && section == 0 && row == 0) {
return UITableViewCellEditingStyleInsert;
}
return UITableViewCellEditingStyleDelete;
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
NSIndexPath *ip = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView beginUpdates];
if (editing) {
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:ip]
withRowAnimation:UITableViewRowAnimationLeft];
} else {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:ip]
withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView endUpdates];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ClientsControllerCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc ] initWithStyle:UITableViewCellStyleValue1reuseIdentifier:CellIdentifier] autorelease];
}
int section = indexPath.section;
int row = indexPath.row;
if (self.editing && section == 0 && row == 0) {
cell.textLabel.text = #"Add Me";
cell.detailTextLabel.text = #"Detail text";
} else {
cell.textLabel.text = #"Test me";
cell.detailTextLabel.text = #"Detail text";
}
return cell;
}
But as soon as I go into EDIT mode, "both" cells end up with an editing style of UITableViewCellEditingStyleInsert.
If I change my logic and append the new cell to the END - then it correctly draws the cells with a DELETE style and the new cell get's an INSERT.
Either way, tableView:editingStyleForRowAtIndexPath gets invoked 4 times. In fact, if I insert the new cell into section:0 row:0, this method gets called with section:row 0:0, 0:1, 0:0, 0:0. Whereas if I append the new cell into section: row:1, this method gets called with section:row 0:0, 0:1, 0:0, 0:1.
What am I missing? I should be able to insert a row and catch it right? For some reason, I can't see section=0 row=1 come through a second time.
-Luther

There's another question on StackOverflow that appears to ask essentially the same thing: SO 1508066.
The answer there claims it's nonstandard to put the Insert row at the top; it should go at the bottom instead. I'm not sure I agree with that contention, but it's certainly the path of least resistance.

Related

Insert new row in UITableView when tapping first row

I'm new to iOS development. Therefore it would be nice if your answer could be as detailed as possible. My problem is the following: I want the cell in my second section to always say "add new row". If you click this cell a new row should be added on top. My current code is
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (section == 0) {
return 1;
}
else {
return [entries count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"Cell";
static NSString *todayTableIdentifier = #"Notification";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
UITableViewCell *todaycell = [tableView dequeueReusableCellWithIdentifier:todayTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
NSString *entry;
if (indexPath.section == 0) {
entry = [notifications objectAtIndex:indexPath.row];
cell = todaycell;
} else {
entry = [entries objectAtIndex:indexPath.row];
}
cell.textLabel.text = entry;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int selectedRow = indexPath.row;
NSLog(#"row pressed %d", selectedRow);
NSArray *paths = [NSArray arrayWithObject:
[NSIndexPath indexPathForRow:2 inSection:2]];
if (selectedRow == 0)
{ [entries addObject:#"a new Entry"];
[[self tableView] insertRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationTop];
}
else {
[[self tableView] deleteRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationTop];
}
}
Unfortunately, my app keeps crashing.
2013-04-11 16:52:39.717 MyStore[12924:c07] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-2380.17/UITableView.m:861
2013-04-11 16:52:39.718 MyStore[12924:c07] *** Terminating app due to uncaught exception ' NSInternalInconsistencyException', reason: 'attempt to delete row 2 from section 2, but there are only 2 sections before the update'
*** First throw call stack:
(0x1faa012 0x13e7e7e 0x1fa9e78 0xb6d665 0xbb43c 0xca1f6 0xca271 0x5c6b 0xcb285 0xcb4ed 0xad55b3 0x1f69376 0x1f68e06 0x1f50a82 0x1f4ff44 0x1f4fe1b 0x1f047e3 0x1f04668 0x1bffc 0x21bd 0x20e5 0x1)
libc++abi.dylib: terminate called throwing an exception
That's normal, besides adding a new row (visually), you need to update your Data Source (the notifications array in this case). The UI is just the visualization of your Data Source. So:
[notifications addObject:#"a new Entry"];
[[self tableView] insertRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationTop];
And when you delete:
[notifications removeObjectAtIndex:indexPath.row];
[[self tableView] deleteRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationTop];

Empty sortDescriptors and table view sections

There is something within table sections and sortDescriptors that is driving me crazy. Any help will be much appreciated ;)
What I'm looking for is just displaying an "add" row at the bottom of each self.fetchedResultsController.fetchedObjects section. I'm using a sectionNameKeyPath to show sections in my table view. Up to here everything works perfectly. I've implemented this technique in other parts of my app and works pretty well.
However, the problem comes up when there is no data returned by the FRC and core data doesn't return any entity. If I try to add my first managed object, then app breaks with the following error:
The number of sections contained in the table view after the update
(1) must be equal to the number of sections contained in the table
view before the update (1), plus or minus the number of sections
inserted or deleted (1 inserted, 0 deleted). with userInfo (null)
This is my code so far:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
if ([self.fetchedResultsController.fetchedObjects count] == 0) {
return 1;
}
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if ([self.fetchedResultsController.fetchedObjects count] == 0) {
return 1;
}
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects] + 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.fetchedResultsController.fetchedObjects count] == 0 ) {
static NSString *identificadorCelda2 = #"CeldaNuevoGasto";
UITableViewCell *celda = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:identificadorCelda2];
if (celda == nil) {
celda = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:identificadorCelda2];
}
return celda;
}
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:indexPath.section];
NSInteger nsection = [sectionInfo numberOfObjects];
if (indexPath.row == nsection) {
static NSString *identificadorCelda2 = #"CeldaNuevoGasto";
UITableViewCell *celda = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:identificadorCelda2];
if (celda == nil) {
celda = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:identificadorCelda2];
}
return celda;
}
static NSString *CellIdentifier = #"CeldaGasto";
CeldaGastos *cell = (CeldaGastos *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = (CeldaGastos *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
} .....
For sure I'm doing something wrong, but I can not figure it iut. Do I need to modify NSFetchedResultsController delegate methods as well to respond to additions? Or is it something easiser. Thanks ;)
Update
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
NSInteger nsection = [self.fetchedResultsController.sections count];
switch(type) {
case NSFetchedResultsChangeInsert:
if (nsection == 1 && [sectionInfo numberOfObjects] == 1) {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
if (!nsection)
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
If I understand your situation correctly, then you indeed will have to change the NSFetchedResultsController delegate methods as well.
Are you inserting new sections into table view using the -insertSections:withRowAnimation: method of your table view from some of the NSFetchedResultsController's delegate callbacks?
I do not know exactly what your NSFetchedResultsControllerDelegate's implementation is, but for example consider this situation step by step:
Initially you have no entities fetched, and your -numberOfSectionsInTableView: method returns 1 for your 'add' row.
After you've inserted an entity and the NSFetchedResultsController's delegate receives its -controller:didChangeSection:atIndex:forChangeType: callback, where you insert a new section into table view using -insertSections:withRowAnimation:
The table view asks its dataSource by calling the -numberOfSectionsInTableView: method expecting it to rerurn a value increased by 1 (since you are inserting 1 new section). But the method does still return 1 because the 'add' row is not taken into account anymore.
That will result exactly in the error which you are having.
I guess calling -deleteSections:withRowAnimation: to remove the 'add' row when it is not needed anymore will solve your problem.

Display some grouped tableview sections according the state of the first section, how?

I would a grouped tableview such that:
when the only row of the first section is associated to a state, say A, I can see only this first section with, possibly, some text under it (in a footer for example);
when this state changes, I would see other sections under the first;
How could I achieve this? Some code/link to obtain something similar?
Thanks,
Fran
No problem, just add some if else logic in all your tableView Datasource and delegate methods.
For example like this:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if (!canUseInAppPurchase || isLoading) {
return 1;
}
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (!canUseInAppPurchase || isLoading) {
return 1;
}
if (section == 0) {
// this will be the restore purchases cell
return 1;
}
return [self.products count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
cell = ...
NSString *cellText = nil;
if (!canUseInAppPurchase) {
cellText = #"Please activate inapp purchase";
}
else if (isLoading) {
cellText = #"Loading...";
}
else {
if (section == 0) {
cellText = #"Restore purchases";
}
else {
cellText = productName
}
}
cell.textLabel.text = cellText;
return cell;
}
and if you want to add or remove the second section you could use a simply [tableView reloadData]; or this smoother variant:
[self.tableView beginUpdates];
if (myStateBool) {
// activated .. show section 1 and 2
[self.tableView insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)] withRowAnimation:UITableViewRowAnimationTop];
}
else {
// deactivated .. hide section 1 and 2
[self.tableView deleteSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)] withRowAnimation:UITableViewRowAnimationBottom];
}
[self.tableView endUpdates];
be careful, you have to change the data in your datasource first. And this code will add 2 sections. But you can adopt it to your needs easily.

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.