I have been working on this problem now for many days and am in need of assistance because I'm not sure how to proceed. The problem I'm facing (which will become clearer as you read through this question) is I'm not sure how obtain an existing NSManagedObject if it already exists when creating a new one.
Let's take a step back to look at the Model and the premise of the app is simple; allow users to create "Transactions" which has a name, amount, occasion and date - upon creating a new Transaction, a user should be allowed to choose an existing name. The Data Model:
The transaction has a relationship to the Person entity (whoBy), a link to the Occasion Entity (occasion) and a link to the Item Entity (itemType).
The App is a simple 2 Tabb-ed Table View Controller - the first tab contains all of the information, sectioned off by dates. The second tab contains just the "Name" information. There is a plus button and when the user presses that, they're presented with a Modal View to add in the Name, Occasion, Amount and Date. They press save and it saves it to the Core Data Database.
Problem Creating a new transaction with a new name creates this perfectly. Creating a new transaction with an existing name (with the user manually typing in the identical name) creates a new object in the first tab as a separate entry (correct) but in the name tab, it creates a duplicate name for that person. When you click on either of the duplicate names, it shows both transactions. The problem here is that it's looking like it's creating a new Person object every time instead of referencing.
Code
I have gone through MULTIPLE iterations to try get this working and here is the latest code, using subclasses of NSManagedObject.
The managedObjectContext is obtained from the App Delegate.
The name, event, amount and date are stored in their own text fields.
AddEntry.m - Save Method
- (IBAction)save:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
Transaction *transaction = [NSEntityDescription insertNewObjectForEntityForName:@"Transaction" inManagedObjectContext:context];
Item *itemType = [NSEntityDescription insertNewObjectForEntityForName:@"Item" inManagedObjectContext:context];
Person *p = (Person *)[Person personWithName:self.nameTextField.text inManagedObjectContext:context];
Occasion *o = (Occasion *)[Occasion occasionWithTitle:self.occasionTextField.text andEventDate:self.dateTextField.text inManagedObjectContext:context];
transaction.whoBy = p;
occasion.title = self.occasionTextField.text;
transaction.occasion = o;
itemType.amount = self.itemTextField.text;
transaction.item = itemType;
transaction.wasReceived = @(self.isReceivedSegment.selectedSegmentIndex == 0);
NSError *error = nil;
if (![context save:&error])
{
NSLog(@"Can't save! %@ %@", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
This of course calls the Person NSManagedObject subclass and here is where I have gone through a few iterations.
Person NSManagedObject Subclass - Iteration 1 - Predicates
+ (Occasion *)eventWithName:(NSString *)title andEventDate:(NSString *)date inManagedObjectContext:(NSManagedObjectContext *)context;
{
Occasion *occasion = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Occasion"];
request.predicate = [NSPredicate predicateWithFormat:@"title = %@", title];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *occasions = [context executeFetchRequest:request error:&error];
if (!occasions)
{
// Handle Error
}
else if (![occasions count])
{
occasion = [NSEntityDescription insertNewObjectForEntityForName:@"Occasion" inManagedObjectContext:context];
occasion.title = title;
}
else
{
occasion = [occasions lastObject];
}
occasion.dateOfEvent = date;
return occasion;
}
Problem This somewhat works. I placed a breakpoint and the returned "person" that gets returned here and to the save method in the Add Entry is the same if the object already exists and new if it's a new object, but when it comes to the second tab, it's still adding the a second entry for the same person.
Iteration 2 - objectID
With trying to obtain the Object ID, I'm getting the same result of duplicated entries in the second tab:
+ (Person *)personWithName:(NSString *)name inManagedObjectContext:(NSManagedObjectContext *)context;
{
Person *person;
NSManagedObjectID *personID = person.objectID;
if (!personID)
{
person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
person.name = name;
return person;
}
else
{
return person;
}
}
In case it matters, the second tab's fetchRequest is as follows:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Occasion" inManagedObjectContext:managedObjectContext];
fetchRequest.entity = entity;
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:NO];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
fetchRequest.fetchBatchSize = 20;
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
EDIT I'm wondering if the fact that I don't have a predicate here in the second tab fetchRequest means it's illustrating ALL objects..
Of course, I'm future proofing this because in the first tab, when I click on a cell, I'd like to have that "Transaction" passed across so I can edit the field.
This is driving me crazy and is really stopping my app from progressing, so any assistance here would be massively appreciated!
EDIT: Data Model Included
Ok this is what is happening... U add first transaction and for a name U provide Jack, then U add second transaction (T further in text) and add name John, after U add third T with name Jack and add another with name John. If U take a look at the tables in the database you will see that Transitions table has 4 entries cause You added 4 transitions, but Persons has only two. It has 2 cause you added a filter on Persons input so that it cannot contain two same names. And the mistake you made is that U tried to fetch from Transition table all entries. Of course U get 4 names (2 Johns and 2 Jacks) cause there R 4 entries in Transition table and each is associated with a Person record. If U want to fetch all persons you shouldn't fetch it from the Transition table, you should do it from Persons table OR you should add [r setReturnsDistinctResults:YES]; on your fetch request in case U R fetching from Transition table... I think there is a way to set distinct by which property but I don't know the syntax. So the problem is really in what do You want to do, do you want to present all the persons U have in your database, or you want to present names on your transitions which means that same people could have made different transitions. See...