Stange animation when deleting UITableViewCell - iphone

I'm using this code to delete a UITableViewCell, but when I swipe to delete, it is showing the minus sign on the right first, then deleting.
I took a video to help illustrate my point: Video on YouTube
- (void)setEditing:(BOOL)editing animated:(BOOL)animate
{
[self.tableView setEditing: !self.tableView.editing animated:YES];
if (self.tableView.editing)
[self.navigationItem.leftBarButtonItem setTitle:#"Done"];
else
[self.navigationItem.leftBarButtonItem setTitle:#"Edit"];
}
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
PFObject *routine= [self.routineArray objectAtIndex:indexPath.row];
[routine deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
[self.routineArray removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
// [MKInfoPanel showPanelInView:self.view type:MKInfoPanelTypeError title:#"Routine Deleted" subtitle:#"You have succesfully deleted the routine!" hideAfter:2];
} else {
NSLog(#"%#", error);
}
}];
}
}
Edit:
- (void)loadData
{
PFQuery *query = [PFQuery queryWithClassName:#"Routine"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
self.routineArray = [objects mutableCopy];
[self.tableView reloadData];
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
-(void)addRoutine
{
PFObject *routine = [[PFObject alloc] initWithClassName:#"Routine"];
[routine setObject:self.entered forKey:#"name"];
[routine saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
[self loadData];
} else {
// There was an error saving the routine.
}
}];
}

It looks like there are two issues. The first it looks like -deleteInBackgroundWithBlock: is taking a noticeable amount of time to execute it's block after the delete button is pressed. You can try deleting the dataSource object and tableView row before deleting the data from the core data store if you aren't using a NSFetchedResultsController
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
PFObject *routine = [self.routineArray objectAtIndex:indexPath.row];
[self.routineArray removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[routine deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
//[MKInfoPanel showPanelInView:self.view type:MKInfoPanelTypeError title:#"Routine Deleted" subtitle:#"You have succesfully deleted the routine!" hideAfter:2];
} else {
NSLog(#"%#", error);
}
}];
}
}
You can also use a different animation if you prefer something other than fading out the opacity of the row. If you are targeting iOS 5.0 only, you can use UITableViewRowAnimationAutomatic to have UIKit attempt to choose the best looking animation given the circumstances.
The other issue looks like editing mode is turned back on after delete is pressed. You shouldn't need to override -setEditing:animated: so try removing that method completely.
In your -viewDidLoad: you can do the following to get editing behavior for free:
self.navigationItem.leftBarButtonItem = self.editButtonItem;
See:
An Example of Deleting a Table-View Row
UIViewController Class Reference: -editButtonItem
It should also be noted that when you are checking the editing status, you should use the isEditing accessor.
To avoid calling -reloadData, you just add the single new object to your dataSource array, then add a tableView row, then save it to the core data store. This is simply the opposite of what you had to do when deleting a row from the table view. This will work if you need to append the routine object to the end of the tableView and there is no custom sort order. Otherwise, you must insert the routine object into self.routineArray at the desired index and then create the proper indexPath to insert the tableView row at the desired location within the tableView.
- (void)addRoutine
{
PFObject *routine = [[PFObject alloc] initWithClassName:#"Routine"];
[routine setObject:self.entered forKey:#"name"];
[self.routineArray addObject:routine];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:([self.routineArray count]-1) inSection:0];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath withRowAnimation:UITableViewRowAnimationAutomatic
[routine saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
[self loadData];
} else {
// There was an error saving the routine.
}
}];
}

Related

uiTableView crashes the second time in deleting a cell

I'n new in iPhone, I'm trying to delete a cell from my UITableView, the first time it deletes well, but the second time it gives me 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 (3) must be equal to the number
of rows contained in that section before the update (3), 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).'
here is my code of table:
- (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
Book_own *temp= (Book_own *)[self.books objectAtIndex:indexPath.row];
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[books removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
isDeleted = #"true";
deletedBook_id = temp.bo_id;
[self viewDidLoad];
}
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
}
}
- (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.
NSDictionary *dictionary = [listOfItems objectAtIndex:section];
NSArray *array = [dictionary objectForKey:#"myBooks"];
return [array count];
}
in ViewDidLoad I wrote the following code:
NSDictionary *mbooks = [NSDictionary dictionaryWithObject:books forKey:#"myBooks"];
NSDictionary *mgroups = [NSDictionary dictionaryWithObject:filteredParts forKey:#"myBooks"];
listOfItems = [[NSMutableArray alloc] init];
[listOfItems addObject:mbooks];
[listOfItems addObject:mgroups];
Can anyone tell me how to solve this issue??
Thanks in advance.
What is happening is that you are either not deleting the item or you are deleting only one item when you allow multible deleting. You can adjust by checking if it is really deleted as below or reload data if not:
// 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
BOOL success = [self removeFile:indexPath];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
if (!success) [tableView reloadData];
}
}
This method then removes the datasource item (it is from my own project so the names should be adjusted:
-(BOOL) removeFile:(NSIndexPath *)indexPath{
// Removes from the datasource and the filesystem
NSURL *fileURL = [self.dataSource objectAtIndex:indexPath.row];
NSError *error;
BOOL success = [[NSFileManager defaultManager] removeItemAtURL:fileURL error:&error];
if (success) {
[self.dataSource removeObjectAtIndex:indexPath.row];
} else {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error" message:[error localizedDescription] delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
[alert release];
[self.dataSource removeObjectAtIndex:indexPath.row];
}
return success;
}
If I'm reading your code correctly your data source is listOfItems. You should remove the row from your tables data source. A general rule is that when you remove or add items to a UITableView you must update the databsource.
[listOfItemsremoveObjectAtIndex:indexPath.row];
First, do not call [self viewDidLoad]; This method should not be called manually.
I think you are not calling the update method to your table view. This might fix your problems:
[tableView beginUpdates];
[books removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
edit: you also need to look at your code more closely. You are deleting a record from your datasource array directly from the row of the indexPath, which could be problematic considering the fact that your tableView has two sections.
The error says that there should be 1 fewer row after the deletion. It fails because the datasource code is not consistent in how it reports about the model.
Look at how you answer numberOfRowsInSection. Correctly, I think, first picking out the array that the section index represents, then answering that array's count.
The same logic must apply on the delete. The array you delete from needs to be the array indicated by the indexPath.section. Consider this:
- (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
// the array you manipulate here must be the same array you use to count
// number of rows, and the same you use to render rowAtIndexPath
NSDictionary *dictionary = [listOfItems objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:#"myBooks"];
// in order to be edited, it must be mutable
// you can do that on the fly here, or set it up as mutable
// let's make it temporarily mutable here:
NSMutableArray *mutableCopy = [array mutableCopy];
if (editingStyle == UITableViewCellEditingStyleDelete) {
[mutableCopy removeObjectAtIndex:indexPath.row];
// if it's setup mutable, you won't need to replace the immutable copy in the dictionary.
// but we just made a copy, so we have to replace the original
[listOfItems replaceObjectAtIndex:indexPath.section withObject:[NSArray arrayWithArray:mutableCopy]];
// and so on

UItableView load data on scroll

In my app I am getting the data from the web-service and I have to display it in UITableView.
But the condition here is I have to display only 10 records initially,then once the user scroll down I have to load more records.I tried searching it but didn't get any useful answer.
I agree that I will use -
(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
to display the value,but how will I fetch only 10 records from the service and then other record based on scroll.
Please provide some pointers or sample code.
Thanks
In case if some one need it,i was able to solve my problem this way.
First thing is you need the server configuration in such a way so that it should return 10 data at a time based on the row which is visible in TableView.
This is the tableView delegate which get called and returns the visible cells in tableView
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
int lastRow=[nameArray count]-1;
if(([indexPath row] == lastRow)&&(lastRow<[categoryArray count]))
{
if(tableView==m_pDetailsTableView) {
savedScrollPosition=lastRow;
startCellValue=[NSString stringWithFormat:#"%d",0];
endCellValue=[NSString stringWithFormat:#"%d",[nameArray count]+10];
[self connectToServer]; //Method to request to server to get more data
}
}
}
savedscrollPosition variable stores the variable as a point where you want to scroll the table view after load of data.
You should read about lazy loading. The code is available at Apple's website. Download it here
http://developer.apple.com/library/ios/#samplecode/LazyTableImages/Introduction/Intro.html
check the code of
- (void)loadImagesForOnscreenRows
method.
It uses the same approach you need. It gets the current scroll position of the table view and on the basis of that, it will get the cells displayed on the screen and their indexPath. On the basis of that you will be able to show those cells which are shown in the screen.
For showing 10 rows, a simple calculation is required.
Just insert the new data into your datasource
see below
If you're using xml - check out XMLReader - turn XML into an NSDictionary
this sample code below uses AFNetworking (which is non blocking)
https://github.com/AFNetworking/AFNetworking/
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate)
{
[self fetchMoreData];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self fetchMoreData];
}
- (void)fetchMoreData
{
if ([resultArray count] > 0)
{
NSArray *visiblePaths = [myTableView indexPathsForVisibleRows];
NSIndexPath *lastRow = [visiblePaths lastObject];
// Check whether or not the very last row is visible.
NSInteger numberOfSections = [myTableView numberOfSections];
NSInteger lastRowSection = [lastRow section];
NSInteger lastRowRow = [lastRow row];
NSInteger numberOfRowsInSection = [myTableView numberOfRowsInSection:lastRowSection];
if (lastRowSection == numberOfSections - 1 &&
lastRowRow== numberOfRowsInSection - 1) {
DLog(#"it's the last row");
if ([resultArray count]%10 == 0) { // use a divider based on your pagination
[self fetchNextPage];
}
}
}
}
-(void)getFeeds{
ENTER_METHOD;
[resultArray removeAllObjects];
//reset this
NSString *url = [NSString stringWithFormat:#"/webserviceurl.xml?offset=0"];
[httpClient getPath:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self parseFeedsXMLString:operation.responseString];
// offset = offset + 10; // ONLY if there's results increment
} failure:^(AFHTTPRequestOperation *operation, id responseObject){
NSString *detailError=nil;
}];
}
-(void)fetchNextPage{
NSString *url = [NSString stringWithFormat:#"/webserviceurl.xml?offset=%d",offset];
[httpClient getPath:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
DLog(#"operation.responseString:%#",operation.responseString);
[self parseNextFeedsXMLString:operation.responseString];
// offset = offset + 10; // ONLY increment if there's results
} failure:^(AFHTTPRequestOperation *operation, id responseObject){
}];
}
- (void)parseFeedsXMLString:(NSString *)xmlString
{
NSError *parseError = nil;
NSDictionary *xmlDictionary = [XMLReader dictionaryForXMLString:xmlString error:&parseError];
DLog(#"xmlDictionary:%#",xmlDictionary);
resultArray = [[NSMutableArray arrayWithArray:[[xmlDictionary objectForKey:#"feed"] objectForKey:#"entry"]]retain];
[myTableView reloadData];
}
-(void)parseNextFeedsXMLString:(NSString *)xmlString
{
NSError *parseError = nil;
NSDictionary *xmlDictionary = [XMLReader dictionaryForXMLString:xmlString error:&parseError];
DLog(#"xmlDictionary:%#",xmlDictionary);
//[resultArray insertObject:e atIndex:[resultArray count]];
NSMutableArray *results = [NSMutableArray arrayWithArray:[[xmlDictionary objectForKey:#"feed"] objectForKey:#"entry"]];
if ([results count]) {
page++;
for (NSDictionary *dict in results) {
[resultArray insertObject:dict atIndex:[results count]];
}
}
[myTableView reloadData];
}
If I correctly understand your question ,you can do the following.
1 ) implement scrollViewDidScroll
2 ) check for visible rows in that
3 ) if you found the last row just call the web service for loading
more data
4 ) on getting the data just reload the table
Try it .
You can adjust the return value of tableView:numberOfRowsInSection: method, every time you want to insert ten rows, you can plus 10 to the return value.
sorry for my poor english.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger lastSectionIndex = [tableView numberOfSections] - 1;
NSInteger lastRowIndex = [tableView numberOfRowsInSection:lastSectionIndex] - 1;
if ((indexPath.section == lastSectionIndex) && (indexPath.row == lastRowIndex)) {
i = i +10;
NSString *unescaped = mySearchBar.text;
NSString *escapedString = [unescaped stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
[PServiceAPI searchKeyWord:escapedString withOffSet:i Handler:^(NSArray *results, NSError *error) {
if (error == nil) {
[arBase addObjectsFromArray:results];
[myTableView2 reloadData];
}
}];
}
}

How to delete the NSManagedObject instance from Data Source?

I am trying to override tableView:commitEditingStyle:editingStyleforRowAtIndexPath: and having trouble implementing the deletion of the actual instance of a NSManagedObject that is represented in that row.
Apple says it should be done with the following code(Shown Here):
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object at the given index path.
NSManagedObject *eventToDelete = [eventsArray objectAtIndex:indexPath.row];
[managedObjectContext deleteObject:eventToDelete];
// Update the array and table view.
[eventsArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
// Commit the change.
NSError *error = nil;
if (![managedObjectContext save:&error]) {
// Handle the error.
}
}
}
When I mimick this sample code in my app, every line works except for one line. The one line is: [bowlerArray removeObjectAtIndex:indexPath.row];. I get the error "Receiver type 'NSArray' for instance message does not declare a method with selector 'removeObjectAtIndex'".
What should that one line of code be?
Note: My line NSManagedObject *eventToDelete = [bowlerArray objectAtIndex:indexPath.row]; works just fine.
Update: Posting my actual code:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = [appDelegate managedObjectContext];
NSManagedObject *objectToDelete = [bowlerArray objectAtIndex:indexPath.row];
[moc deleteObject:objectToDelete];
[bowlerArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] 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
}
}
NSArray is immutable, so you can't modify it.
removeObjectAtIndex is not part of the NSArray API, because it would modify it.
You need an NSMutableArray to be able to do that.
If I do this :
NSMutableArray *arMu = [NSMutableArray arrayWithObjects:#"0", #"1", #"2", #"3", #"4", nil];
[arMu removeObjectAtIndex:0];
self.bigLabel.text = [arMu objectAtIndex:0];
the bigLabel is showing 1 for the index 0.
You error message is suggesting that you still have a NSArray instead of an NSMutableArray for the variable eventsArray
You can make a NSMutableArray from a NSArray like this :
NSMutableArray *arMu = [[NSMutableArray alloc] initWithArray:someNSArray];

NSFetchedResultsController ignores fetchLimit?

I have a NSFetchedResultsController to update a UITableView with content from Core Data. It's pretty standard stuff I'm sure you've all seen many times however I am running into slight problem. First here's my code:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Article" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:20];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(folder.hidden == NO)"];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sort1 = [NSSortDescriptor sortDescriptorWithKey:#"sortDate" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sort1, nil]];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
[fetchRequest release];
controller.delegate = self;
self.fetchedResultsController = controller;
[controller release];
NSError *error = nil;
[self.fetchedResultsController performFetch:&error];
if (error) {
// TODO send error notification
NSLog(#"%#", [error localizedDescription]);
}
The problem is that initially the store has no entities as it downloads and syncs from a webservice. What happens is that the NSFetchedResultsController fills the table with over 150 rows of entities from the store, which is how many the webservice returns. But I am setting a fetch limit of 20 which it appears to be ignoring. However, if I close out the app and start again with data already in the store, it works fine. Im my delegate i do this:
#pragma mark -
#pragma mark NSFetchedResultsControllerDelegate methods
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
Which is pretty much copy-paste from Apple's dev documents, any ideas what's goin on?
I know this is an old question, but I have a solution for it:
Since there is a known bug in NSFetchedResultsController that doesn't honor the fetchlimit of the NSFetchRequest, you have to manually handle the limiting of records within your UITableViewDataSource and NSFetchedResultsControllerDelegate methods.
tableView:numberOfRowsInSection:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
NSInteger numRows = [sectionInfo numberOfObjects];
if (numRows > self.fetchedResultsController.fetchRequest.fetchLimit) {
numRows = self.fetchedResultsController.fetchRequest.fetchLimit;
}
return numRows;
}
controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert:
if ([self.tableView numberOfRowsInSection:0] == self.fetchedResultsController.fetchRequest.fetchLimit) {
//Determining which row to delete depends on your sort descriptors
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:self.fetchedResultsController.fetchRequest.fetchLimit - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
}
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
...
}
}
This is an old question but I just ran into it myself (in iOS 5). I think you're running into the bug described here: https://devforums.apple.com/message/279576#279576.
That thread provides solutions based on whether you have a sectionNameKeyPath or not. Since I (like you) didn't, the answer is to decouple the tableview from the fetchedResultsController. For example, instead of using it to determine the number of rows:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[self.fetchedResultsController sections] objectAtIndex:0] numberOfObjects];
just return what you expect:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return fetchLimit;
And in controller:didChangeObject, only insert the new object if the newIndexPath is within your fetchLimit.
These will still crash in some situations, like several inserts, or move over limit,...
You have to save all the changes to 4 sets, and calculate another 4 arrays and delete/update/insert to tableView before -[UITableView endUpdates]
Some thing like (assume there is only one section):
NSUInteger limit = controller.fetchRequest.fetchLimit;
NSUInteger current = <current section objects count>;
NSMutableArray *inserts = [NSMutableArray array];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"row < %d", limit];
if (insertedIndexPaths.count) {
NSUInteger deletedCount = 0;
for (NSIndexPath *indexPath in insertedIndexPaths) {
if (indexPath.row >= limit) continue;
current++;
if (current > limit) {
deletedCount++;
current--;
[deletedIndexPaths addObject:[NSIndexPath indexPathForRow:limit - deletedCount inSection:indexPath.section]];
}
[inserts addObject:indexPath];
}
}
if (movedIndexPaths.count) {
for (NSIndexPath *indexPath in movedIndexPaths) {
if (indexPath.row >= limit) {
[updatedIndexPaths addObject:[NSIndexPath indexPathForRow:limit - 1 inSection:indexPath.section]];
} else {
[inserts addObject:indexPath];
}
}
}
[updatedIndexPaths minusSet:deletedIndexPaths];
[deletedIndexPaths filterUsingPredicate:predicate];
[updatedIndexPaths filterUsingPredicate:predicate];
[_tableView insertRowsAtIndexPaths:inserts withRowAnimation:UITableViewRowAnimationFade];
[_tableView reloadRowsAtIndexPaths:[updatedIndexPaths allObjects] withRowAnimation:UITableViewRowAnimationNone];
[_tableView deleteRowsAtIndexPaths:[deletedIndexPaths allObjects] withRowAnimation:UITableViewRowAnimationFade];
[_tableView endUpdates];
deletedIndexPaths = nil;
insertedIndexPaths = nil;
updatedIndexPaths = nil;
The problem you have is that you are calling before loading fetchedResultsController charge the full data so it shows you everything you need to do is load all the information and then call fetchedResultsController
Example
- (void)viewDidLoad {
[super viewDidLoad];
// Loading Articles to CoreData
[self loadArticle];
}
- (void)ArticleDidLoadSuccessfully:(NSNotification *)notification {
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort(); // Fail
}
[tableView reloadData];
}
From apple doc: https://developer.apple.com/reference/coredata/nsfetchrequest/1506622-fetchlimit
If you set a fetch limit, the framework makes a best effort, but does not guarantee, to improve efficiency. For every object store except the SQL store, a fetch request executed with a fetch limit in effect simply performs an unlimited fetch and throws away the unasked for rows.
I filed a bug report with Apple back in 2014 on iOS 6/7 about this issue. As many others have noted, it's still a bug on iOS 9 and 10. My original bug report is still open too with no feedback from Apple. Here is an OpenRadar copy of that bug report.
Here's a fix I've used with success but it will get called multiple times. Use with caution.
#objc func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates() // Only needed if you're calling tableView.beginUpdates() in controllerWillChangeContent.
if controller.fetchRequest.fetchLimit > 0 && controller.fetchRequest.fetchLimit < controller.fetchedObjects?.count {
controller.performFetch()
// Reload the table view section here
}
}
}
This is my trick:
I set the NSFetchedResultsController's delegate after 'save' method on the NSManagedObjectContext instance is called.
Set an observer on your UIViewController with a name: eg. 'Sync'
after saving your context, post a notification with that name: 'Sync' and trigger a function (in your viewcontroller) that set the delegate
ps. remember to remove that observer if you don't need it anymore

Coredata on iphone: I can delete data only if I restart the application

I've a simple applications that lets you create groups of people form persons in your AddressBook... So Groups and Persons are in a one-to-many relationships, since a Group can have multiple persons. That's not a many-to-many since I create my own model of Person.
Adding data works without problems.
Deleting data doesn't. If I create a new Person, I must restart the app to delete it or the to delete the Group that Person belongs. Otherwise I get a "EXC BAD ACCESS" in the console. With NSZombieEnabled in the enviroment I get -[CFString release]: message sent to deallocated instance 0x75140d0.
I start with the CoreData stuff automatically created by XCode, create the RootViewController (subclass of TableViewController), I pass it the context and put it in a NavigationController.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Creo il controller root e gli passo il context
RootViewController *rvc = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
rvc.context = [self managedObjectContext];
//Creo il navcon, gli associo il root e lo rendo visibile
navCon = [[UINavigationController alloc] initWithRootViewController:rvc];
[window addSubview:navCon.view];
[window makeKeyAndVisible];
[rvc release];
return YES;
}
The RootViewController shows the Groups, then clicking on a row lets you modify persons in that group, passing the "nuovogruppo" (the Group Model associated with that row)
- (void)showPersoneControllerWithGruppo:(Gruppo *)nuovogruppo {
PersoneController *pc = [[PersoneController alloc] initWithStyle:UITableViewStylePlain];
pc.gruppo = nuovogruppo;
pc.context = self.context;
pc.delegate = self;
//NSLog(#"%#",[gruppi objectAtIndex:indexPath.row]);
[self.navigationController pushViewController:pc animated:YES];
[pc release];
}
And this is how I delete the person (gruppo is the Group model these persons belong to, persone is an array filled with these persons on viewDidLoad, removeGPObject is an accessor method generated by XCode (Group to Persons relationship))
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[gruppo removeGPObject:[persone objectAtIndex:indexPath.row]];
[persone removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
NSError *error;
[context save:&error];
}
}
I hope someone can help me...
UPDATE
Since I was having errors about messages sent to already released instances I tried commenting out all the [... release] lines and finally find out what was causing the problem. The problem was in the creation method of the record and not in the deleting method. Here is the method I use to create it.
The line that was causing the roblem is [NomeCognome release]
I'd be very grateful if someone could explain me why this line crashes the app.
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier {
if (property == kABPersonPhoneProperty) {
ABMultiValueRef phoneProperty = ABRecordCopyValue(person,property);
NSString *phone = (NSString *)ABMultiValueCopyValueAtIndex(phoneProperty,identifier);
NSString *firstName = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *surname = (NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
NSString *NomeCognome = [NSString stringWithFormat:#"%# %#", firstName, surname];
[firstName release];
[surname release];
Persona *persona = (Persona *)[NSEntityDescription insertNewObjectForEntityForName:#"Persona" inManagedObjectContext:context];
persona.ABRR = phone;
persona.NomeCognome = NomeCognome;
[phone release];
[NomeCognome release]; //This line makes the app crash!!! Why???
[gruppo addGPObject:persona];
NSError *error;
[context save:&error];
[self.delegate PersoneControllerDidSave:self];
[self loadContentAndReload:YES];
[self dismissModalViewControllerAnimated:YES];
}
Why that line crashes the app?
Use reloadData method at the end as the following.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[gruppo removeGPObject:[persone objectAtIndex:indexPath.row]];
[persone removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
NSError *error;
[context save:&error];
[tableView reloadData];
}
It may work now.