NSFetchedResultsController from multiple entities and updating model - ios

I have existing core data model that has Entity Video.
I want to update an app and I would like to add another entity to the object called Project.
It seems that I achieved this using core data light migration.
Now I would like to Video to be child of the Project. And finally in the UITableView I would like to display Projects as Section headers and Videos as rows.
What would be the best way to accomplish it?
Currently I am using NSFetchedResultsController to query the core data.
thank you

If I'm not mistaken, you can achieve this kind of change using the lightweight migration. You have to create a one-to-many ordered relationship between the Project entity and the Video entity. You can still use NSFetchedResultsController to fetch a list of projects and then traverse the relationship with the Video entity to get the associated objects. It would look more or less like this:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Project" inManagedObjectContext: context];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setRelationshipKeyPathsForPrefetching: #"videos"];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc]
initWithFetchRequest: fetchRequest
managedObjectContext: context
sectionNameKeyPath: nil
cacheName: nil];
We're setting up an NSFetchRequest object to prefetch the "videos" relationship which will save us some time when accessing Video entities. Then, after retrieving a list of Project entities, you would access them in tableView:cellForRowAtIndexPath:
- (NSInteger) numberOfSectionsInTableView: (UITableView*) tableView
{
return [self.fetchedResultsController.fetchedObjects count];
}
- (NSInteger) tableView: (UITablView*) tableView numberOfRowsInSection: (NSInteger) section
{
Project *project = [self.fetchedResultsController.fetchedObjects objectAtIndex: section];
return [project.videos count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
Project *project = [self.fetchedResultsController.fetchedObjects objectAtIndex: indexPath.section];
Video *video = [project.videos objectAtIndex: indexPath.row];
...
}

Related

Core Data one to many relationship get Data

I am storing JSON Data to CoreData with a one-to-many relationship. I am able to get the data back, using NSFetchRequest and fast enumeration, but the data is not coming in the order format I need and it cannot be used in my UITableViewCells how can i do this
this is my code
this is my datamodel
https://www.dropbox.com/s/1e1ujrjxtkjy9h9/Screen%20Shot%202015-04-29%20at%205.14.31%20pm.png?dl=0
_appDelegate = [[UIApplication sharedApplication]delegate];
_managedObjectContext = [_appDelegate managedObjectContext];
NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"DealSection"];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"sectionID == %#",_sectionID]];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSArray * sortDescriptor = [NSArray arrayWithObjects:nil];
[fetchRequest setSortDescriptors:sortDescriptor];
NSError * error;
NSArray * data = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
_fetchData = data;
for (DealSection * section in _fetchData) {
for (Deal * deal in [section alldeals]) {
NSLog(#"%#",[deal nameAttribute]);
}
}
i put this all code in the ViewDidLoad
here am getting the data from NSLog but my problem am able to print the data but am not able to pass the data to the table view
The JSON snippet that you have posted shows that (contrary to my supposition in the comments, apologies) each Deal can have many DealSections.
Your DealSection entity has a relationship entitled alldeals that is currently a to-one relationship. I.e. each DealSection can have only one Deal. I think this should be to-many. To take an example, the deal in the JSON you posted has sections with ID=6 (name="Services") and ID=8 (name="Wellness"). Suppose you had another deal with a section with ID=6 - do you want to use establish the relationship with the existing DealSection, or to create a new DealSection?
Currently your code creates a new DealSection, but I think you probably want the relationship with the existing DealSection. For that to work, you will need to
a) amend your data model to make the relationship many-many.
b) amend the code where you store the data for the many-many relationship, so that it starts by trying to fetch a DealSection with the correct ID. If one is found, add that DealSection to the Deal's sectionRelation. If it is not found, created a new DealSection and add that to the Deal.
EDIT
To display the data in a tableView, you need to implement three datasource methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// A table view section for each DealSection:
return [self.fetchData count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
DealSection *dealSection = self.fetchData[section];
NSArray *deals = [dealSection.alldeals allObjects];
return [deals count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Amend this identifier to match your cell prototype
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Set up the cell...
DealSection *dealSection = (DealSection *)self.fetchData[indexPath.section];
NSArray *deals = [dealSection.alldeals allObjects];
Deal *deal = (Deal *)deals[indexPath.row];
cell.textLabel.text = deal.nameAttribute;
return cell;
}
This is basic table view stuff, so if any of it is unclear I recommend the tutorial I mentioned in comments.

How to fetch Core Data by the order the data was added and display in that order in a UITableView?

I am displaying an entity called Skills in a UITableViewController.
I fetch the results like this in the viewDidLoad:
-(void)fetchTableData {
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Skills" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
self.skillsArray = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
}
Also my cell for index path is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
// Configure the cell...
Skills *skill = self.skillsArray[indexPath.row];
// Skills is a NSManagedObject, I added the Skills.h file.
[cell.textLabel setText:skill.nameOfSkill];
return cell;
}
And I am adding new NSManagedObject *newSkill to Core Data by using UIAlertView with a text field in the delegate method:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
NSLog(#"Add button clicked");
NSString *newSkillText = [alertView textFieldAtIndex:0].text;
NSManagedObjectContext *context = [self managedObjectContext];
Skills *newSkill = [NSEntityDescription insertNewObjectForEntityForName:#"Skills" inManagedObjectContext:context];
newSkill.nameOfSkill = newSkillText;
[self.skillsArray addObject:newSkill];
} else {
// Do something else
}
[self.tableView reloadData];
}
Every time I reload the data the cells are displaying the data in the order the data was added but if dismiss the view controller and return the cells display the data in a different order than added? The weird part is that I am using this same exact code to add core data and retrieve it in another UITableViewController and it never displays out of order. The data added in this UITableViewController is as follows: I am pushing to another UIViewController and add the information there and then dismiss back to the tableview. In this code I am adding the information while in the view controller is being presented, maybe that could have something to do with it?
Also I know I could add an NSSortDiscriptor such as:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:#"nameOfSkill" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
but it does it by the name and I want by the way it was added without having to add another attribute for index cause I never did that to my other data and it always displays in the order it was added.
You'll have to add an Attribute to sort on...either an updating, incrementing counter, or the timestamp of the insertion. If you subclass NSManagedObject, ou can write this value in -awakeFromInsert. Then your tableview's fetch request will sort on that attribute.
You won't get the data as it is. You will have to insert a field of "Time" and you can sort according to it.
OR
Add a unique field of 'data_id' . Always check the count before inserting the data. And give the data_id accordingly adding 1 to the count. Then after fetching the data from core data sort it as per data_id.
You can do as per you like.
For the desired result you need to sort it according to timestamp or primary key which is auto-generated by core data database.
Core Data makes its own primary key - you don't have to add one. You can retrieve it with
NSManagedObjectID *moID = [managedObject objectID];

NSSortDescriptor reorders cells alphabetically after viewing detail view and returning

I've got a more or less basic Master Detail app. I've followed this tutorial to get familiar with Core Data, and now I'm trying reorder my cells on my Master TVC. Everything is working fine, including the successful reordering of my cells. However, when I dig down and view one of the detail VCs, I return to the original, alphabetized ordering. I believe it has something to do with the NSSortDescriptor "sortDescriptor" that was included in the tutorial. I am not sure how to remove it, or how to give it different characteristics. Any help is appreciated. Below is my NSFetchedResultsController method.
-(NSFetchedResultsController*) fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Top" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"topName" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
fetchRequest.sortDescriptors = sortDescriptors;
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
EDIT:
After much research over the past few days, I'm realizing it's more of an issue with my moveRowAtIndexPath method. Anyone have any suggestions or recommendations of working with Core Data and the ability to reorder cells? Also, does this require custom tableviewcell class?
Your table view is sorted based on a name. Given that the names are fixed, if you reorder cells 'by hand' and then return to the table view, the sorting based on the name is reestablished.
To get what you want you'll need to add a field to your Core Data model called something like sortIndex. You then sort the table with a sortDescriptor based on sortIndex; you can initialize sortIndex to the creation order. When the User reorders cells through the UI, you'll also change the sortIndex for all the impacted cells/managed-objects. Then, since sortIndex is part of the Core Data model, when the User ends and restarts your App, their preferred sorting will be reestablished.
Sometimes, for modeling reasons, you don't want to include a sortIndex directly in some managed object. For example, a Person has first and last names but not a sortIndex (in fact). You can create an association, perhaps a DisplayPreferences managed object, with a one-to-one mapping between a Person and a DisplayPreferences and having a sortIndex in DisplayPreferences.
A few things you need to check here:
You need to actually perform the fetch the first time by calling performFetch: on the NSFetchedResultsController. Either do this in the getter method or it should be done in viewDidLoad.
Have you implemented the required NSFetchedResultsControllerDelegate methods properly? That includes controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:, controller:didChangeSection:atIndex:forChangeType: and controllerDidChangeContent:. The code is all boilerplate but you need to make sure it is there.
What do your UITableViewDatasource methods look like? Make sure the sections and row counts look like this:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[[self fetchedResultsController] sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
}
Make sure you are using the indexPath to grab your object in the tableView:cellForRowAtIndexPath: method. It should look something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myCellIdentifier"];
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = rowPackage.title;
return cell;
}

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

Adding attribute value to core data IF value already exists

I have to say that everyone on the forum has been really helpful with my attempts at learning core data.
I am adding attribute values to my core data entities and creating a relationship when the user selects a row as shown below:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
UIAlertView *messageAlert = [[UIAlertView alloc]
initWithTitle:#"Row Selected" message:#"Added to Routine!" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
NSManagedObjectContext *context = [self managedObjectContext];
// Create a new device
ExcerciseInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath];
NSManagedObject *routineEntity = [NSEntityDescription insertNewObjectForEntityForName:#"Routines"inManagedObjectContext:context];
NSManagedObject *routineEntityDetail = [NSEntityDescription insertNewObjectForEntityForName:#"RoutinesDetails" inManagedObjectContext:context];
[routineEntityDetail setValue:routineEntity forKey:#"routineinfo"];
[routineEntity setValue: RoutineText forKey:#"routinename"];
[routineEntityDetail setValue: info.details.muscle forKey:#"image"];
How would I include an IF statement whereby if the routinename already exists the new entry would be added to the existing relationship?
Is this easily possible from the current code? So the Test Routines will be grouped instead of showing separately and the detail view would include both entries. Would NSPredicatebe appropriate here? Or perhaps the use of distinctUnionOfObjects?
You thoughts and comments will be appreciated.
AS REQUESTED -
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Routines" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"routinename" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName: nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
You don't need to care about this, you can simply insert a new RoutineDetails object and Core Data takes care of the rest. When you auto-generate the NSManagedObjectsubclasses of your DB tables, Core Data automatically generates accessor methods for all relationships, where you can add and remove single or multiple instances of RoutineDetails from Routines.
To create NSManagedObject subclasses, go to your ManagedObjectModel click on Editor->Create NSManagedObject subclass… and create subclasses for your entities:
In your new subclass, you'll see methods like:
#interface Routines (CoreDataGeneratedAccessors)
- (void)addRoutineDetailsObject:(RoutineDetails *)value;
- (void)removeRoutineDetailsObject:(RoutineDetails *)value;
- (void)addRoutineDetails:(NSSet *)values;
- (void)removeRoutineDetails:(NSSet *)values;
Than you can access all the RoutineDetails directly through the Routines object (you'll get an NSSet of RoutineDetails), and furthermore you can add new objects with
[routineEntity addRoutineDetailsObject:routineEntityDetail];
not caring if an object already exists or not. Core Data will do the right thing.
Comment regarding Core Data: Core Data can have a lot boilerplate code for simple things like fetching. Take a look at the MagicalRecord project, which is much less verbose and does wonders.
EDIT: Whenever you previously created NSManagedObject, now you can directly use your own classes, i.e. Routines. That way you'll have access to all the "new functionality".

Resources