I have implemented the tableView:editActionsForRowAtIndexPath: function, however i experience intermittently the indexPath passed to this function is nil. When i try to access indexPath.row , its get a value 18446744073709551615.
I am querying for the object in the array I use to build the table. and getting the crash --[__NSArrayM objectAtIndex:]: index 18446744073709551615 beyond bounds [0 .. 1]'
There was a case where I was reloading my table using [table reloadData] an editAction call then would get passed a nil indexPath, however this is still intermittently happening.
Is there any way i can enforce the correct indexPath to be passed to tableView:editActionsForRowAtIndexPath:
You have to implement this - (void)tableView:(UITableView *)tableView commitEditingStyle method where data array need to be adjusted.
For details look at this article Inserting and Deleting Rows and Sections
You can handle this by removing the item corresponding to
the row from data array at first and then sending deleteRowsAtIndexPaths:withRowAnimation: to the table view.
Try to insert below lines of code inside - (void)tableView:(UITableView *)tableView commitEditingStyle method:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ( editingStyle== UITableViewCellEditingStyleDelete) {
// remove item from array at first
[dataArray removeObjectAtIndex:indexPath.row];
// then delete table row from tableview
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
Hope it will work!
Related
I am trying to delete a cell and remove a section from a UITableView with the following code:
[self.tableView beginUpdates];
[arraymasterFeedFullDetails removeObjectAtIndex:cellSelectedIndexPath.section];
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:cellSelectedIndexPath.section]withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
Here,cellSelectedIndexPath is NSIndexPath variable which is pointing to the section is currently selected. arraymasterFeedFullDetails is my datasource and only one row per each section.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [arraymasterFeedFullDetails count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
When you have a data source(arraymasterFeedFullDetails in your case), the thing you need to do to delete an item, is to remove object from data source and then reload the table.
You can reload the table using [self.tableView reloadData];
So your code would be like this:
[arraymasterFeedFullDetails removeObjectAtIndex:cellSelectedIndexPath.section];
[self.tableView reloadData];
When you have a data source(arraymasterFeedFullDetails in your case), the thing you need to do to delete an item, is to remove object from data source and then either reload the whole table, or, remove the cells from the datasource with animation.
This in general will produce a visually better result.
The reloadData method will reload the whole table, without animation.
Currently shown cells will be simply redrawn. E.g.:
[self.tableView reloadData];
You won't need to update the tableview itself, as this will be simply redrawn.
In order to remove just "edited away cells", with an animation effect, you can use the method deleteRowsAtIndexPaths: withRowAnimation:
E.g.:
// Overridden to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[_list2 removeObjectAtIndex:indexPath.row]; // remove row from array
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade]; // remove cell from UITableView
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
You could also remove rows from your mutable array, then call
deleteSections:withRowAnimation:
between beginUpdates and endUpdates in order to delete a whole section from your tableview;
In both animated deletion approaches, you will need to make sure that the data source has the same count of the tableview cells (after the deletions) before doing any animation which will update the tableview.
I'm working on an app that will update and add user's coordinate to a UITableView when the user has traveled over a preset distance interval. The coordinates will be automatically added to an NSMutableArray, and I use the array to update the table.
Everything load up and work fine (I can edit the table by moving and re-ordering the rows) but whenever I chose to delete a specific row, the program crash with the error "libc++abi.dylib: terminating with uncaught exception of type NSException".
- (void) tableView:( UITableView *) tableView commitEditingStyle:( UITableViewCellEditingStyle) editingStyle forRowAtIndexPath:( NSIndexPath *) indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSArray *items = [[G5SharedStore sharedStore]allCoords];
G5SharedStore *item = items[indexPath.row];
[[G5SharedStore sharedStore]removeItem:item];
//THIS IS WHERE THE ERROR OCCURS
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
The last line is what causes the error, but I'm new to objective-c, so I'm not sure how to fix this. Any help would be appreciated. Let me know if you need more details.
Thanks in advance.
**Edit:
Ok, I played around with the code this morning and it works. The problem is that the array keeps adding more and more items, but the tableview doesn't, unless I go back out and click "show table" button again to refresh it. So whenever I delete something in the tableview, the table's size is inconsistent with the array's size therefore I get the error. Here's my new problem, I tried to solve the above problem by making the table automatically update its data using:
[tableView reloadData];
The table does update, but it keeps adding blank cells ... with no data in it. Here's where I added the above:
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#" UITableViewCell"
forIndexPath:indexPath];
NSArray *items = [[G5SharedStore sharedStore] allCoords];
G5SharedStore *item = items[indexPath.row];
cell.textLabel.text = [item description];
[tableView reloadData];
return cell;
}
Since I just started iOS programming 1 month ago, I could be wrong. So please guide me.
Thanks again everyone
I can see two problems:
Firstly this article recommends modifying the table view before the data-model (you are doing it the other way round):
It must do two things:
Send deleteRowsAtIndexPaths:withRowAnimation: or insertRowsAtIndexPaths:withRowAnimation: to the table view to direct
it to adjust its presentation.
Update the corresponding data-model array by either deleting the referenced item from the array or adding an item to the array.
Secondly you don't appear to be calling beginUpdates and endUpdates around that call. To quote the reference:
Note the behavior of this method when it is called in an animation
block defined by the beginUpdates and endUpdates methods.
Instead of delete row delete object from the array and reload table
- (void) tableView:( UITableView *) tableView commitEditingStyle:( UITableViewCellEditingStyle) editingStyle forRowAtIndexPath:( NSIndexPath *) indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSArray *items = [[G5SharedStore sharedStore]allCoords];
G5SharedStore *item = items[indexPath.row];
[[G5SharedStore sharedStore]removeItem:item];
//Remove this
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
//Add This Line
[tableView reloadData];
}
}
I have a table view backed by NSFetchedResultsController.
The situation is as follows.
I have implemented "swipe to delete" a row. Delegate methods of NSFetchedResultsController and those of table view's are called. But the thing is, I don't want to delete a Core Data Entity, I just want to mark its boolean flag since I need this entity in other parts of application, and want table view to delete row with animation. However, according to error messages it is against logic like if you are updating an entity, update a row, not delete it.
Is there any elegant way to achieve it? Or the only option is reload the table?
Implementation of swipe to delete
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
Issue *issue = [self.fetchedResultsController objectAtIndexPath:indexPath];
[issue setHasBeenDeleted:[NSNumber numberWithBool:YES]];
[issue setPurchased:[NSNumber numberWithBool:NO]];
[self.managedObjectContext saveToPersistentStore:nil];
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id) anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
...
case NSFetchedResultsChangeUpdate:
[self.tableView deleteRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
break;
NSFetchedResultsControllerDelegate
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
[self.tableView endUpdates];
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.tableView beginUpdates];
}
Error message
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (5) must be equal to the number of rows contained in that section before the update (4), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
In the fetch request you are giving to your FRC, include this boolean value as a part of your predicate. For example, [NSPredicate predicateWithFormat:#"shouldDisplay = YES"]. Then whenever you want to remove are particular object, just set your shouldDisplay (or whatever you call it) to NO, and you will receive an FRC delegate callback for controller:
didChangeObject: with a NSFetchedResultsChangeDelete change type. In this callback, you will do the following.
NSIndexPath *indexPath = [controller indexPathOfObject:anObject];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
Your object will remain in core data, but it will be filtered out of this particular table view because of your predicate on the boolean flag.
EDIT: You also need to implement two of the FRC delegate methods to notify your table view to begin and end updates. This will correct the error you are receiving. Like so:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
I couldn't get Pat Goley's solution to work, though perhaps I didn't implement it properly. After some experimentation, the solution I came up with is contained in a single callback method:
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle != .Delete { return }
let obj = fetchedResultsController.objectAtIndexPath(indexPath) as! Entity
obj.needsDeletion = true
try! managedObjectContext.save()
try! fetchedResultsController.performFetch()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
This has the disadvantage that performFetch() must be called, but without it, it's crash city. For very large recordsets, an approach similar to Pat's is probably best.
I want to delete a row from my tableview. I cannot find a solution that works from the other simialr questions here.
I know I need to use the following code, But I cant seem to figure out how to properly finish this line of code to make it work.
[dailyIncomeArray removeObjectAtIndex:indexPath.row];
But what goes where is says "dailyIncomeArray"?
[what-goes-here removeObjectAtIndex:indexPath.row];
I have a getData method that populates my tableview. Next I want to be able to swipe to the left and hit that red delete button. Thanks in advance for your help!
Heres the rest of the method
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[dailyIncomeArray removeObjectAtIndex:indexPath.row];
[self.tableView reloadData];
//[tableView deleteRowsAtIndexPaths:[NSMutableArray arrayWithObject:results] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
Here's the error I'm receiving. I don't understand what this means.
No visible #interface for 'NSArray' declares the selector 'removeObjectAtIndex:'
I´m quite new to iOS development and I´m having a terrible time by trying something that should be easy; to add an extra row in a TableView everytime the user clicks on one of the existing rows. There is no real purpose on that action, I´m just wanting to understand the behaviour of TableView.
So I did the following:
I used a Split View-based template and changed the number of rows to 30 in the RootViewController.
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 30;
}
The method tableView:didSelectRowAtIndexPath looks in the following manner:
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
/*
When a row is selected, set the detail view controller's detail item to the item associated with the selected row.
*/
NSMutableArray* paths = [[NSMutableArray alloc] init];
NSIndexPath *indice = [NSIndexPath indexPathForRow:30 inSection:0];
[paths addObject:indice];
detailViewController.detailItem = [NSString stringWithFormat:#"Second Story Element %d with all its information and bla bla bla", indexPath.row];
[[self tableView] beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[[self tableView] endUpdates];
}
When I execute the program and click on one of the elements, I receive the following error:
*** 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 (30) must be equal to the number of rows contained in that section before the update (30), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted).'
I did not change any other part of the code that the template provides.
I read quite extensively the documentation from Apple and the responses to the following questions:
Add a row dynamically in TableView of iphone
and
how to properly use insertRowsAtIndexPaths?
The second question seems to address the same problem, but I´m not capable to understand what is happening. What do they mean with dataSource? The response that I understand better says the following:
It's a two step process:
First update your data source so numberOfRowsInSection and cellForRowAtIndexPath will return the correct values for your post-insert data. You must do this before you insert or delete rows or you will see the "invalid number of rows" error that you're getting.
What does this update of the data source implies?
Sample code would be HIGHLY appreciated, because I´m totally frustrated.
By the way, all that I´m trying has nothing to do with entering the editing mode, has it?
You need to keep the count returned by tableView:numberOfRowsInSection: in sync!
So when you have 30 rows and then tell the tableview to insert a new row you need to make sure tableView:numberOfRowsInSection: will now return 31.
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section
{
return self.rowCount;
}
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.rowCount++;
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
In practice you would probably use an array to track your rows return [self.rows count]; etc
The answer is quite simple. When you want to modify a table view you need to perform two simple steps:
Deal with the model
Deal with table animation
You already perform the second step. But you have missed the first one. Usually when you deal with a table you pass it a data source. In other words some data to display within it.
A simple example is using a NSMutableArray (it's dynamic as the name suggests) that contains dummy data.
For example, create a property like the following in .h
#property (nonatomic, strong) NSMutableArray* myDataSource;
and in .m synthesize it as:
#synthesize myDataSource;
Now, you can alloc-init that array and populate it as the following (for example in viewDidLoad method of your controller).
self.myDataSource = [[NSMutableArray alloc] init];
[self.myDataSource addObject:#"First"];
[self.myDataSource addObject:#"Second"];
Then, instead of hardcoding the number of rows you will display (30 in your case), you can do the following:
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
return [self.myDataSource count];
}
Now, in you didSelectRowAtIndexPath delegate you can add a third element.
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.myDataSource addObject:#"Third"];
[[self tableView] beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[[self tableView] endUpdates];
}
It looks like one big problem is with tableView:numberOfRowsInSection:. You need to return the correct number of rows in that method.
To do that, it's usually best to maintain an NSArray or NSMutableArray of items for the table view so in that function, you can say: return [arrayOfValues count];. Keep the array as a property of your view controller class so that it's readily accessible in all methods.
The array can also be used in cellForRowAtIndexPath:. If you have an array of NSString, you can say cell.text = [arrayOfValues objectAtRow:indexPath.row];.
Then, when you want to add an item to the table view, you can just add it to the array and reload the table, e.g. [tableView reloadData];.
Try implementing this concept and let me know how it goes.
You can Also do that for dayanamic table cell
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [arrayStationStore count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIndentyfire;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIndentyfire];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentyfire];
}
cell.textLabel.text = [arrayStationStore objectAtIndex:indexPath.row];
return cell;
}
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Check if current row is selected
BOOL isSelected = NO;
if([tblStationName cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryCheckmark)
{
isSelected = YES;
}
if(isSelected)
{
[tblStationName cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
[arrayReplace removeObject:indexPath];
NSLog(#"array replace remove is %# ",arrayReplace);
}
else
{
[tblStationName cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
[arrayReplace addObject:indexPath];
NSLog(#"array replace add is %# ",arrayReplace);
}
return indexPath;
}