IOS Core Data Cardinality issue - ios

I'm writing a simple application using TableViews and Core Data that shows a list of students in the first level and when clicked on a cell, it shows the names of the courses that student takes. I set student->courses as toMany, courses->student as toOne. This is how i pass the Student entity to course view controller for using its managedObjectContext:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Student *info = [_fetchedResultsController objectAtIndexPath:indexPath];
CoursesTableViewController *courseViewController = [[CoursesTableViewController alloc] initWithStudentInfo:info];
[self.navigationController pushViewController:courseViewController animated:YES];
}
This is how i create courses inside the courses view controller(I want only three courses as named below, numberofrow is supposed to assure that):
- (void)viewDidLoad
{
[super viewDidLoad];
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][0];
int numberofrow = [sectionInfo numberOfObjects];
NSLog(#"%d",numberofrow);
NSLog(#"%d",[self.fetchedResultsController sections].count);
if(numberofrow <=2){
NSLog(#"creating courses");
[self insertNewObject:#"comp319"];
[self insertNewObject:#"comp314"];
[self insertNewObject:#"comp316"];
}
}
- (void)insertNewObject:(NSString *) str{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:str forKey:#"course_name"];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
How i create the fetchedResultsController inside courses View Controller:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Courses" inManagedObjectContext:self.studentInfo.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"course_name" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.studentInfo.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
Finally the problem is that when I create the first student and click on it, NSLog in viewDidLoad of courses view controller above prints "creating courses", however when i create the second student it does not and also numberofrow prints 3 where it should have printed 0 since it is a new student instance.
Thanks for any help.

Replace
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Courses" inManagedObjectContext:self.studentInfo.managedObjectContext];
[fetchRequest setEntity:entity];
With
// my comment : entity here has to be your student name, why 'course'
// and also replace your search key from 'yourCourseNameKey' with 'yourStudentNameKey'
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Your Student Name" inManagedObjectContext:self.studentInfo.managedObjectContext];
[fetchRequest setEntity:entity];
You need to get number of Students but you query number of Courses

You do not set a predicate on the fetch provided to the fetched request controller so you aren't searching for courses for the selected student and creating then if they aren't there. Not that that is ideal anyway (and wouldn't work because you aren't setting a relationship between the student and all of the courses).
So, most likely you should have a set of courses and a set of students. The relationship should be many:many as a course really has multiple students and each student takes multiple courses (or could anyway).
Now, your students and courses would be created 'up-front' and then your view controllers simply deal with fetching, displaying and connecting (establishing the relationships) the objects.

Related

Core Data to-many relationship fetching

I have the following set up in core data: A Dish entity, with many ingredients, and a Ingredient entity with one dish.
I'm having trouble fetching my to-many relationship data, i.e. the ingredients for a certain dish. I'm trying to populate my collection view cells with the ingredients associated to each dish.
I have my core data model set up as follows:
I have tried various methods to fetch the data.
One of the things I've tried is to do the following: Dish *dish = [self.fetchedResultsController objectAtIndexPath:indexPath]; and then NSLog dish.ingredients (which returns a NSSet), but the returns the whole core data item, i.e., the createdAt date, the name and everything, together with the unformatted version of the ingredients for that dish.
Another thing i tried was to create a new instance of the Ingredient entity with Ingredient *ingredient = [dish.ingredients valueForKeyPath:#"ingredientName"];, but that returns an unformatted string (I want it UTF8 encoded before it's displayed because it contains special characters).
A lot of solutions when googling suggests using NSPredicate, but I just can't figure out how to use that. I'm still new to core data, and XCODE in general, so any help here would be appreciated, i.e. where do you implement the NSPredicate, and what would the correct predicate be in my case?
A bit of supporting code:
my collectionViewController:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSManagedObjectContext* context = [RKObjectManager sharedManager].managedObjectStore.mainQueueManagedObjectContext;
self.managedObjectContext = context;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Dish" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"createdAt" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
cellForItemAtIndexPath:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
FOFPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"photo" forIndexPath:indexPath];
self.object = [self.fetchedResultsController objectAtIndexPath:indexPath];
Dish *dish = [self.fetchedResultsController objectAtIndexPath:indexPath];
Ingredient *ingredient = ???;
cell.backgroundColor = [UIColor lightGrayColor];
[cell.imageView setImageWithURL:[NSURL URLWithString:[[NSString stringWithFormat:#"http://192.168.0.3:4000%#", dish.dishImage] description]] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
}];
cell.titleLabel.text = dish.dishName;
cell.headerLabel.text = dish.dishHeader;
cell.ingredientsLabel.text = [NSString stringWithFormat:#"%#", nil];
NSLog(#"%#", ingredient);
[self.collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
return cell;
}
The simple way is your first approach and then only use the attributes that you care about from the ingredients. Core Data is more about programming with objects than it is about the underlying database, so attempting to simulate DB queries isn't usually the right approach.
I also recommend getting rid of all the valueForKey stuff and using actual object properties so that the code is a bit clearer and the compiler can help you out more with data types.
If you're looking for Ingredients only for a one specific Dish, you have to set your fetchRequest for Ingredients (and not Dish) and set the predicate on Dish. To make the following code work, you may need first to Create NSManagedObject Subclasses (with the help of Xcode Editor button).
#import "Dish.h"
#import "Ingredient.h"
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Ingredient"];
[fetchRequest setFetchBatchSize:20];
//if dishname are unique values, set your predicate on it.
//You can also set your predicate on the dishID if dishName are not unique values (several dishes with a same name)
NSString *theDishIWant = #"Ratatouille"; //whatever Dish name you want
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"dish.dishName == %#", theDishIWant];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"ingredientname" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[RKObjectManager sharedManager].managedObjectStore.mainQueueManagedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
NSLog (#"%#", _fetchedResultsController);
return _fetchedResultsController;
}
Your configureCell: atIndexPath: method may look like this:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Ingredient *ingredient = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = ingredient.ingredientName;
cell.detailTextLabel.text = ingredient.ingredientValue;
}

Strange bug: uncommenting an NSPredicate within an NSFetchedResultsController will empty the UITableView

I have a strange bug: if I uncomment my NSPredicate, the resulting UITableView is empty.
My data Model is the following:
Category <-->> Feed <-->> Post
I am fetching the Posts. Post.feed is a Post's Feed. Feed has an rss NString property.
Here's the code:
- (NSFetchedResultsController *)fetchedResultsController {
// Set up the fetched results controller if needed.
if (_fetchedResultsController == nil) {
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Post"
inManagedObjectContext:_globalMOC];
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"feed.rss == %#", _detailItem.rss];
[fetchRequest setPredicate:predicate];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_globalMOC
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = aFetchedResultsController;
self.fetchedResultsController.delegate = self;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate.
// You should not use this function in a shipping application, although it may be useful
// during development. If it is not possible to recover from the error, display an alert
// panel that instructs the user to quit the application by pressing the Home button.
//
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
return _fetchedResultsController;
}
I only see results if I uncomment the NSPredicate. I tried with LIKE, ==, =, with double and single quotes around %#...
The best would be to directly compare the Feed object...
Anyone can help me?
Solved here.
The problem was that the NSFetchedResultsController was initialized once before the _detailItem was even set.

NSFetchedResultsController not informed about model changes

Got somehow lost on something what worked in other projects and now stopped working.
I've got a controller (A) (subclass of NSObject) which has a NSFetchedResultsController for entities of class kClassCycleQuestionDetails.
My ManagedObjectContext is initiated with NSMainQueueConcurrencyType.
In another controller (B) I start inserting entities of the above class and start a save on every tenth object.
[self.moc performBlock:^{
[self prepareNewCycle];
}];
The context posts the NSManagedObjectContextDidSaveNotification (received in B) but I never see the controllers delegate method - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller called in A.
Both controller share the same managed object context, so I should see model changes?!
Has anyone an idea.
Edited:
For clarification: prepareNewCycle inserts a cycle entity into the context and than
adds entities of kClassCycleQuestionDetails to the cycle entity (to-many relationship).
Here's the code for the controller A's fetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:kClassCycleQuestionDetails inManagedObjectContext:self.moc];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
[fetchRequest setPredicate:[self predicateForExamCycleMode:self.cycle.examQuestionModeValue cycleId:self.cycle.cycle_id]];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:kQCQDCycleQuestionDetails_id ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.moc
sectionNameKeyPath:nil
cacheName:NSStringFromClass([self class])];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[NSFetchedResultsController deleteCacheWithName:NSStringFromClass([self class])];
NSError *error = nil;
if (![_fetchedResultsController performFetch:&error]) {
ALog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}

Display records for Core data 1:M relationship

I'm sadly stuck with Core data. I've built a 1:M relationship, let's say Company and Employees.
I create the Company, then I create the Employee:
Employees *employee = (Employees *)[NSEntityDescription insertNewObjectForEntityForName:#"Employees" inManagedObjectContext:myObjectContext];
employee.name = employeeName;
employee.company = company;
NSError *error = nil;
if (![myObjectContext save:&error])
{
// Handle the error.
NSLog(#"error: %#", error.description);
}
If I analyze the sql file, I can see that the Parent column for the Employees record is correctly populated with the Company identifier.
Now I would like to select a Company in my tableview and show its Employees in another tableview. I implement the NSFetchedResultsController as follows:
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil)
{
return fetchedResultsController;
}
//retrieve context
if (myObjectContext == nil)
{
id appDelegate = (id)[[UIApplication sharedApplication] delegate];
self.myObjectContext = [appDelegate managedObjectContext];
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Employees" inManagedObjectContext:myObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *myFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:myObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
self.fetchedResultsController = myFetchedResultsController;
fetchedResultsController.delegate = self;
return fetchedResultsController;
}
and I call it in viewDidAppear:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSError *error = nil;
//call the fetch
if (![[self fetchedResultsController] performFetch:&error])
{
NSLog(#"Error with fetchedResultsController: %#, %#", error, [error userInfo]);
exit(-1);
}
[self.tableView reloadData];
}
The problem is that all the Employees, and not only those in the selected Company, are displayed.
Any ideas?
Your fetch request is setup to fetch all employees with no condition so just as you are seeing, you are going to get all employees in your database. You either need to set a predicate on your fetch request using NSPredicate and have it only select Employees whose Company is the company you selected in the previous view. Or, the approach I would take, you have the Company object selected, pass its managedObjectId or the managedObject itself to your Employee tableview controller and use the relationship you setup to fetch the employees from it, Core Data will fill in any faults and fetch the related employee objects from the database for you.
If you setup your relationship properly calling selectedCompany.employees will return an NSSet which you can use as your UITableViewController's data source.
Edit
If there are potentially a large number of employees in the relationship you may want to stick with the fetched results controller approach and setup the predicate appropriately in order to obtain the memory efficiency benefits that NSFetchedResultsController brings to the table.

Issue with NSPredicate in fetchedResultsController method

I'm creating an app that lets a salesperson order stock for their customers from their iPhone.
The user navigates to a customer and creates an order. A blank tableview appears and the user then adds items to the tableview by selecting them from an inventory screen.
When they add an item to the order, the navigation controller pops the view and shows the order view again. The user should only be able to see orders for that customer.
I originally built the app entirely in sqlite and I achieved this by using the query
SELECT PRODUCT FROM TRANSLINE WHERE CUSTOMERACCNO = ?
I have now moved onto Core Data and I need to achieve the same functionality. I'm trying to implement this behaviour in the fetchedResultsController method using NSPredicate, but I can't seem to get it working - all I get is a blank screen. However, when I don't implement it, I get ALL orders i.e. orders for every customer, not just this one.
Here's my code :
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Configure the Entity
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TransLine" inManagedObjectContext:__managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
//Configure the predicate
[NSFetchedResultsController deleteCacheWithName:#"Root"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"customerAccountNo == %#", _customerAccountNo];
[fetchRequest setPredicate:predicate];
//Configure Sort Descriptors
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"PRODAC" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:__managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
return __fetchedResultsController;
}
Thanks in advance for all your help.
Well, it seems that in my frustration.... I am, in fact, an idiot. Guess what I forgot to include.
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}

Resources