I am having trouble deleting an object in an NSMutableSet using core Data. I am trying to delete a "player" object in the second section of my tableview. I am getting the error;
Invalid update: invalid number of rows in section 1. The number of
rows contained in an existing section after the update (6) must be
equal to the number of rows contained in that section before the
update (6), 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
Solution
Take a look at my code.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
if (indexPath.section==0) {
}else{
_player = [self.fetchedResultsController.fetchedObjects objectAtIndex: indexPath.row];
[self.managedObjectContext deleteObject:_player];
[self performFetch];
[self.managedObjectContext save:nil];
// here the solution to make it works...
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
}
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch(section){
case 0:
return 4;
case 1:
return [self.fetchedResultsController.fetchedObjects count];
}
return 0;
}
Generally when you need to delete or remove element from a table view, you need to perform a two steps operation:
deal with the model
deal with the table animation
You performed only the first part. To complete you need to perform a call like the following
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
You need to wrap the deleteRowsAtIndexPaths:withRowAnimation: between beginUpdates
and endUpdates if you perform multiple animations for your table, e.g delete, modify, etc. Here it is not the case but you can do it anyway.
When you use core data you could have this for free (you have to write some code) you NSFetchedResultsController with its delegate NSFetchedResultsControllerDelegate. So, when you remove an element with deleteObject call (step 1) the delegate will respond automatically to that change and will perform step 2.
Take a look to at How to use NSFetchedResultsController to have an understanding on how to set up it correctly.
The fix for the code above is to use
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
Hope that helps.
Related
I am having trouble creating an NSPredicate object based on my data model configuration.
I have 2 entity's one called Bob and one called Fred. Bob has a many to many relationship called 'hasFreds' (the reverse is hasBobs) with Fred and Fred has a property hasEaten of type BOOL.
I want to fetch a list of Bob's where every Bob has at least one Fred with hasEaten=YES. If there are no Freds with hasEaten=YES, I want to return all Bobs with no Freds.
This is what I have so far and it doesn't quite work (it doesn't meet the "If there are no Freds with hasEaten=YES, I want to return all Bobs with no Fred" condition):
predicate1 = [NSPredicate predicateWithFormat:
#"(SUBQUERY(hasFreds, $fred, $fred.hasEaten = %#).#count > 0)"
,#YES];
fr = [NSFetchRequest fetchRequestWithEntityName:#"Bob"];
fr.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:NO selector:#selector(compare:)]];
fr.predicate = predicate1;
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fr managedObjectContext:[[self appDelegate] managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
fetchedResultsController.delegate = self;
I think that from a concept point of view your conditions are not really one query. You want to make two queries, one based on the outcome of the other one. This is not really what SQL queries, let alone object graph predicates, were conceived for.
Thus, I believe the simplest and most intuitive solution should be to have two versions of your fetched results controller.
You create the other version by changing the predicate dynamically. (I am using this pattern a lot e.g. for searching.) Because a subquery anyway results in more than one trip to the persistent store, you could just make one efficient fetch to evaluate the condition before setting the predicate for your fetched results controller.
After setting your predicate, remove it if necessary:
if ([context countForFetchRequest:request] == 0) {
request.predicate = nil;
}
// continue creating your FRC with the request
Note that countForFetchRequest is very efficient and takes up no memory.
To update,
self.fetchedResultsController = nil;
[self.tableView reloadData]; // or collection view
If you need to take advantage of the FRC's cache feature, a solution with two separate FRCs following the above pattern would also be feasible.
Thanks to the user Mundi, I was able to come up with a solution. First was to implement a method called getCorrectPredicate.
-(NSPredicate *)getCorrectPredicate{
NSPredicate *predicate1;
NSFetchRequest *fr;
predicate1 = [NSPredicate predicateWithFormat:
#"(SUBQUERY(hasFreds, $fred, $fred.hasEaten = %#).#count > 0)",#YES];
fr = [NSFetchRequest fetchRequestWithEntityName:#"Bob"];
fr.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"date" ascending:NO selector:#selector(compare:)]];
fr.predicate = predicate1;
if([[[self appDelegate] managedObjectContext] countForFetchRequest:fr error:nil] == 0){
// we have a none situation
self.predicateState = [NSPredicate predicateWithFormat:
#"hasFreds.#count=0"];
}else{
self.predicateState = predicate1;
}
return self.predicateState;
}
Second was to check in the NSFetchedResultsController delegate method controllerDidChangeContent whether or not the predicate changes.
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
[_expensesTable endUpdates];
[[[self frc] fetchRequest] setPredicate:[self getCorrectPredicate]];
[self performFetch];
}
I am trying to implement a UICollectionView using supplementaryViews.
I use a Nib file to create my supplementary view. Here is the method in the layout to add the supplementaryView:
NSMutableArray *attributesInRect = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
NSLog(#"%#", attributesInRect);
if ([self.collectionView numberOfItemsInSection:0] > 1 && !self.selectedItem)
{
if ([self.musicList count] > 0)
{
UICollectionViewLayoutAttributes *musicViewAttributes = [self layoutAttributesForSupplementaryViewOfKind:#"MusicView" atIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
musicViewAttributes.size = CGSizeMake(CGRectGetWidth([[UIScreen mainScreen] bounds]), 150);
musicViewAttributes.center = CGPointMake(musicViewAttributes.size.width/2, musicViewAttributes.size.height/2);
musicViewAttributes.zIndex = 0;
[attributesInRect addObject:musicViewAttributes];
}
}
return attributesInRect;
Don't pay attention to the conditions here, only on the array of attributes (attributesInRect). When I do that, my supplementary is properly added to the CollectionView.
My problem is to retrieve an added supplementaryView. On the NSLog, it seams that my supplementaryView is not listed in the array. In this case I can't check it's existence before adding a new one.
I don't really understand why as the documentation specify:
Return Value
An array of UICollectionViewLayoutAttributes objects representing the layout information for the cells and views. The default implementation returns nil.
According to that my supplementaryView should be in that array.
Any idea about that ?
I am currently working on a table list with categories and subcategories, where you can navigate using a drilldown to explore the child categories until you get a detail view.
I got inspiration from iphonesdkarticles.com.
The solution in this blog for the infinite drilldown was populating an array with a plist, and a single UITableView to do the drilldown.
I wanted to use core data with the NSFetchedResultsController instead of the plist.
I got the first list of categories, but when I click one of them, I got an empty table.
I don't know if using the NSFetchedResultsController in this scenario is the most appropriate solution. Maybe I am doing something wrong when I use didSelectRowatIndex:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Categories *category = (Categories *)[fetchedResultsController objectAtIndexPath:indexPath];
MasterViewController *theViewController = [[MasterViewController alloc] initWithStyle:UITableViewStylePlain];
theViewController.CurrentLevel += 1;
theViewController.CurrentTitle = categories.name;
detailViewController.category = category;
[self.navigationController pushViewController:theViewController animated:YES];
}
To create infinite drill down in Core Data, just make an entity a relationship of itself.
I am trying to parse a lot of text files and organize their contents as managed objects. There are a lot of duplicates in the text files, so one of the "collateral" tasks is to get rid of them.
What i am trying to do in this respect is to check whether an entity with the given content exists, and if it doesn't, i create one. However, i have different entities with different attributes and relationships. What i want is a kind of function that would take a number of attributes as an input and return a new NSManagedObject instance, and i wouldn't have to worry if it was inserted into the data store or fetched from it.
Is there one?
I must also say that i am a noob at core data.
Some more detail, if you want:
I am trying to write a sort of dictionary. I have words (Word{NSString *word, <<-> Rule rule}), rules (Rule{NSString name, <->>Word word, <<->PartOfSpeech partOfSpeech, <<-> Ending endings}), parts of speech (PartOfSpeech{NSString name, <<-> Rule rule}) (i hope the notation is clear).
Two words are equal, if they have the same word property, and "linked" to the same rule. Two rules are the same, if they have the same endings and part of speech.
So far i've written a method that takes NSPredicate, NSManagedObjectContext and NSEntityDescription as an input, and first queries the datastore and returns an entity if it finds one, or creates a new one, inserts it into the datastore and returns it. However, in this case I cannot populate the new entity with the necessary data (within that method), so i have to either pass an NSDictionary with the names of attributes and their values and insert them, or return by reference a flag as to whether i created a new object or returned an old one, so that i could populate it with the data outside.
But it looks kind of ugly. I'm sure there must be something more elegant than that, i just couldn't find it. Please, help me if you can.
Your basically on the right path. Core Data is an object graph. There not a lot of dynamic built in. There's also no "upsert". like you surmise, you have to fetch and if it doesn't exist, you insert one.
Here is what I have just started using to handle a fetch-or-create scenario. I am using a top level managed object which contains a few to-many relationships to subordinate objects. I have a class that houses a few arrays of data (those are not shown here). This class is responsible for saving and retrieving to and from core data. When the class is created, I do a fetch-or-create to access my top level NSManagedObject.
#implementation MyDataManagerClass
...
#synthesize MyRootDataMO;
- (MyDataManagerClass *) init {
// Init managed object
NSManagedObjectContext *managedObjectContext = [(MyAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// Fetch or Create root user data managed object
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSError *error = nil;
NSArray *result = [managedObjectContext executeFetchRequest:request error:&error];
if (result == nil) {
NSLog(#"fetch result = nil");
// Handle the error here
} else {
if([result count] > 0) {
NSLog(#"fetch saved MO");
MyRootDataMO = (MyRootDataMO *)[result objectAtIndex:0];
} else {
NSLog(#"create new MO");
MyRootDataMO = (MyRootDataMO *)[NSEntityDescription insertNewObjectForEntityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
}
}
return self;
}
...
I've an entity "Expense" with an attribute (float) called "value". I've a table view populated from CoreData with NSFetchedResultsController.
I'm trying to show in a label (or table header) the total sum of "values" of all my expenses, but I can't implement a solution after reading Apple Docs and googling different forums. For sure beginner disorientation.
Appreciate any indications about the best way to implement this king of operation, or any kind of code that shows similar solution.
first of all. You should use Decimal (the core data name for nsdecimalnumber) and NSDecimalNumber if you want to calculate and store a currency.
I implemented the code you need with float. But you should really change it to NSDecimalNumber. Read this to know why you should do this
If you want to add the expense value to the section header it's easy. You basically take the expense of all objects in the section and sum it up.
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
float expense = 0;
for (NSManagedObject *object in [sectionInfo objects]) {
expense += [[object valueForKey:#"expense"] floatValue];
}
return [NSString stringWithFormat:#"%# [Expense: %f]", [sectionInfo name], expense];
}
Even if you don't use sections in your table this will work. But you should change the returned string then.
I think you should be able to understand it. There is also a more general way to do this. I wrote it a little bit more verbose for you. It uses 3 lines instead of 1 in the for-loop but it does exactly the same thing.
float expense = 0;
for (NSManagedObject *object in [self.fetchedResultsController fetchedObjects]) {
NSNumber *objectExpenseNumber = [object valueForKey:#"expense"];
float objectExpense = [objectExpenseNumber floatValue];
expense = expense + objectExpense;
}
NSLog(#"Expense: %f", expense);
Not really much to explain.
edit: this would be the code if you use NSDecimalNumber
NSDecimalNumber *expense = [NSDecimalNumber decimalNumberWithString:#"0"];
for (NSManagedObject *object in [self.fetchedResultsController fetchedObjects]) {
NSDecimalNumber *objectExpenseNumber = [object valueForKey:#"expense"];
expense = [expense decimalNumberByAdding:objectExpenseNumber];
}
NSLog(#"Expense: %#", expense);