Delete Object from Core data relationship - NSSet - ios

I have a one to many relationship between a Product entity and a Cart Entity.
The user needs to have the opportunity to delete a product from the cart.
I fetch the products like so:
// Fetch request for "Product":
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Product"];
// Fetch only products for the cart:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"inCart = %#", self.cart];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"navn" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:_theManagedObjectContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
and populate the tableview:
Product *prod = (Product *)[self.fetchedResultsController objectAtIndexPath:indexPath];
In the tableview I have a button and when you tap it, that current product, should get deleted... I have the following code in my "delete product method"
-(void)RemoveFromCart:(UIButton *)sender {
UITableViewCell *cell = (UITableViewCell *)sender.superview;
NSIndexPath *indexPath = [_Table indexPathForCell:cell];
Product *prod = (Product *)[self.fetchedResultsController objectAtIndexPath:indexPath];
/*_cart is retrieved when the user pushes the add to cart button, in another viewcontroller, a cart object is then returned and passed to the cart viewcontroller */
NSMutableSet *mutableSet = [NSMutableSet setWithSet:_cart.products];
[mutableSet removeObject:prod];
_cart.products = mutableSet;
[self saveCurrentContext:_theManagedObjectContext];
[_Table reloadData];
}
But nothing happens, when the method is fired.. the product is not removed from the relationship.
How do I manage to fix this, my code is obviously wrong somewhere.

To remove a product from the cart, you can either use
product.inCart = nil;
or
[_cart removeProductsObject:product];
(Both lines are equivalent if the inverse relationships are properly set up.)

[_theManagedObjectContext deleteObject:product];
This only worked for me after Xcode 7.
Maybe because of the changes introduced about the entity+CoreDataProperties category creation?

Related

FetchedResultsController and displaying data from 3 nested entities in one UITableViewCell

Could somebody explain me how can I display in one tableViewCell data from three entities connected by relationship?
I have 3 entities, let it be User, Device, and Alert. Relationships : User has many Devices and these devices have many Alerts.
In my AlertsTableViewController I want to display every alerts with user_name property which is included in User entity.
Should I start my fetch request by setting entity for name User or Alert?.
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"????????" inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:12];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"created_at" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:_managedObjectContext sectionNameKeyPath:nil cacheName:#"Alerts"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
return _fetchedResultsController;
}
My TableView methods:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"AlertsTableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
User *u = [[self fetchedResultsController] objectAtIndexPath:indexPath];
//I know how to display User with details of devices by looping nsset but how to display every alerts with User.name?
for (Device *device in u.devices) {
[cell.textLabel setText:device.someProperty];
}
return cell;
}
When you like to display Alerts, in most simple cases (like yours) you will set the fetch request entity to #"Alert".
To select only Alerts that belong to a given user you will need to define a predicate for your fetch request:
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"device.user = %#",<User object or User objectID>];
The User object or objectID will be known to you from the app user selection, or, if you have another unique identifier for each user (like the userName property) you could include that in your predicate:
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"device.user.name = %#",userNameVariable];
In most cases, the LAST thing you want to do is loop over a set of NSManagedObjects as they will fire faults one by one and give you a really bad user experience (especially in cellForRow...).
Get the data you like to display before you need to supply it to the view.
Edit:
In that spirit, if you like to display the someProperty value in your AlertsTableViewController you should also add:
fetchRequest.relationshipKeyPathsForPrefetching = #[#"device"];
So that the Device will be fetched along with Alert object.
Obviously, you have to change your cellForRow... method to accept Alert objects from the FRC.

Get indexPath of Core Data object in UITableView

I have a UITableView that I'm populating from Core Data with the following NSfetchedResultsController:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:_managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
I have another method attached to an "Add" button that adds a new Core Data item to database. As soon as the object is added, my table updates properly and the new object is shown in its correct spot according to the "date" sort in the fetched results controller. My new object is added using today's date for its "date" attribute. I add the object like this:
NSManagedObject *newEvent = [NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
[newEvent setValue:#"New Client" forKey:#"name"];
[newEvent setValue:[NSDate date] forKey:#"date"];
NSError *error;
if (![context save:&error]) {
NSLog(#"Core Data error! Could not save: %#", [error localizedDescription]);
}
Now, as part of my "Add" method, I need to select the row where the new item was added and segue to an edit screen. Obviously, depending on the other dates of items in the table, it could be anywhere.
I want to select it like this, but I don't have the indexPath:
[self.eventListTable selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];
How do I determine which row (indexPath) my new object was added at so that I can select it properly?
Thanks!
NSFetchedResultsController have a method called: indexPathForObject:
If you have inserted items during your change processing (the FRC delegate methods), select the most recent inserted item. you can determine the index path of the object using the method above.
Or, you could keep the inserted object from your last insert, and in the didChangeContent delegate method, select the inserted item and nullify the variable you kept (so further calles won't trigger the segue).
You will need to find the entity in your NSFetchedResultsController to resolve its indexPath. However to do that, you need to wait for your NSFetchedResultsController to recognize the object. You will probably need to wait for the delegate callback methods from the NSFetchedResultsController to fire and then use -indexPathForObject: to resolve it back to an indexPath and then select the row.
Visually it should work perfectly. The row will appear and then get selected.

ios core data to AZ indexed tableview

I'm trying to show the values of my core data model to an A-Z indexed table based on the first letter on my attributes (similar to the iOS address book app). The "Favorites" entity of my core data model has 2 attributes: username and status. I want to display only the usernames with status = accepted to the A-Z indexed table.
Here is my code:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Favorites" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSString *status = #"accepted";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"status == %#",status];
[fetchRequest setPredicate:predicate];
// Create the sort descriptors array.
NSSortDescriptor *usernameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"username" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:usernameDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Create and initialize the fetch results controller.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:#"username" cacheName:#"Root"];
self.fetchedResultsController = aFetchedResultsController;
fetchedResultsController.delegate = self;
return fetchedResultsController;
}
Now when I'm trying to access the section name I get (null)
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSLog(#"%#",[[[fetchedResultsController sections] objectAtIndex:section] name]);
return [[[fetchedResultsController sections] objectAtIndex:section] name];
}
Also I thing that with that way I will get the the name and not the first char in order to display it as a section title.
You need to access the sectionsInfo object properly:
id <NSFetchedResultsSectionInfo> info =
[fetchedResultsController sections][section];
return [info name];
However, this will give you a heading for each unique name, probably not want you want. Instead, you have to give your entity a transient property e.g. NSString *sectionIdentifier and write a getter for it that returns the first letter of the username attribute.
If want an index from A-Z running down on the right edge of the table view you additionally have to implement:
sectionIndexTitlesForTableView: and
tableView:sectionForSectionIndexTitle:atIndex:.
If you still get null for your titles, maybe they are not set or persisted in your entity? Maybe you got zero results? Maybe your fetchedResultsController is nil? There are a number of flaws in your data model, so this seems quite possible.
Your entity name Favorites is plural. That is not logical, you should name it Favorite as one instance only describes one favourite.
The status is a string which is also very inefficient. Instead, you should use a number and apply some enum scheme.
The username is a property of Favorite. That seems also very messy because presumably, you also have a User entity which has a username attribute. You should use a relationship to model this.
use NSFetchedResultsController's sectionIndexTitles function to get array of first char

Ordering core data objects inside a table view section

I am using NSSortDescriptors to order core data objects and create table view sections depending on a date attribute. Using this kind of SortDescriptor, the table view sections are ordered as expected, but the rows inside the sections are also ordered by the same date attribute. Is there a way to have another ordering system inside each section? I guess the main problem is that core data stores date objects with date+time values, that way there are no objects with exactly the same date value. But I would need to order the objects inside the same section based on another attribute.
Thank you.
And here is my code for the NSFetchedResultsController:
-(NSFetchedResultsController*)fetchedResultsController{
if (_fetchedResultsController != nil){
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSManagedObjectContext *context = self.managedObjectContext;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:#"itemDate" ascending:YES];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc]initWithKey:#"itemName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor,sortDescriptor1, nil];
fetchRequest.sortDescriptors = sortDescriptors;
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:#"sectionIdentifier" cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
EDITED QUESTION
This is the piece of code added to the cellForRowAtIndexPath method:
int indexpathsection = indexPath.section;
[self sortedSectionForIndex:indexpathsection];
NSLog(#"INDEXPATH= %ld", (long)indexPath.section);
And this is the method proposed y Marcus:
- (NSArray*)sortedSectionForIndex:(NSInteger)index
{
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"priority" ascending:YES];
id section = [[self fetchedResultsController] sections][index];
NSLog(#"INDEX********* = %ld", (long)index);
NSArray *objects = [section objects];
NSArray *sorted = [objects sortedArrayUsingDescriptors:#[sort]];
return sorted;
}
The NSFetchedResultsController is meant for the sort order to be controlled via a single sort or set of sorts. It is not intended to do what you are trying to do.
But you can do it, it is just not as clean.
First, the discussion of sorting a section. This does not require you to write your own sorting algorithm:
- (NSArray*)sortedSectionForIndex:(NSInteger)index
{
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"MyKey" ascending:YES];
id section = [[self fetchedResultsController] sections][index];
NSArray *objects = [section objects];
NSArray *sorted = [objects sortedArrayUsingDescriptors:#[sort]];
return sorted
}
With that method, you can ask for a section to be sorted whenever you want to retrieve an object. However that is inefficient as you are sorting every time you want to touch an object which in a UITableViewController is a LOT.
Instead, take this example and integrate it with your NSFetchedResultsController and its delegate methods so that you are storing the sorted arrays and keeping them in sync. That way you are only doing the sort when the data changes instead of on each method call.
Update
The sort code I provided to you does not sort what is inside of the NSFetchedResultsController. It takes what is in the section, sorts it and returns you the sorted array. So you need to use what it returns:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *sorted = [self sortedSectionForIndex:[indexPath section]];
id object = [sorted objectAtIndex:[indexPath row]];
id cell = ...; //Now get your cell reference
//Now update your cell with the data from object
return cell;
}
You can normalize the time component of your attribute in the awakeFromFetchmethod of your NSManagedObject.
- (void) awakeFromFetch {
[super awakeFromFetch];
NSDateComponents* comps = [[NSCalendar currentCalendar] components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:self.itemDate];
self.itemDate = [[NSCalendar currentCalendar] dateFromComponents:comps];
}
In addition, looking at your code, you have to tell the NSFetchedResultsController on what it should base the section breaks. You do this by providing a keyPath for the section breaks when you initialize it. For your case, instead of using sectionNameKeyPath:#"sectionIdentifier", use the first sort key:
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:#"itemDate" cacheName:nil];

Fetching specific data from a selected entry

I am new to iOS Developing, and I'm getting my feet wet in Core Data. I've got an app that I'm slowly piecing together (thanks to help from this site!), but I'm stuck on how to fetch data.
The app:
My app has two main screens, both UITableViews: A "Class List" view where they can add new classes, and an "Add My data model has two entities: Course (for class) and Student, with a to-many relationship from Course-Student. Right now I have it working so that when a I tap on a class in the "Class List" view I am taken to the "Add Students" view where I can add students to the class, but my fetch results controller is returning all students that I have added. My question: how do I format the fetch request in the "Add Students" view to fetch only those students that should belong to that class? Here is the fetched results controller I have right now:
-(NSFetchedResultsController *) fetchedResultsController {
if (_fetchedResultsController !=nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Student" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"name" cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
I'm guessing it has something to do with predicates, but I haven't gotten that far in my learning. Any help at all would be appreciated. Thanks!
You are guessing right. To fetch all students that belong to a particular course,
add the following predicate to the fetch request:
Course *theCourse = ...; // your Course object
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"course = %#", theCourse];
[fetchRequest setPredicate:predicate];
(assuming that the to-one relationship from Student to Course is called "course").

Resources