Core Data one to many relationship get Data - ios

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.

Related

How to show the data of Core Data in a UITableView

I am getting the response from JSON and stored that data by using Core Data. I want to show the data always in a UITableView. Can you suggest me how to display data in a UITableView?
This is the code I'm using.
NSManagedObject * newEntry =[[NSManagedObject alloc]initWithEntity:entityDesc insertIntoManagedObjectContext:self.managedObjectContext];
[newEntry setValue:[dict objectForKey:#"albumName"] forKey:#"albumName"];
[newEntry setValue:[dict objectForKey:#"coverPhotoURL"] forKey:#"coverPhotoURL"];
[newEntry setValue:[dict objectForKey:#"createdDate"] forKey:#"createdDate"];
NSLog(#"Log %#", newEntry);
NSError * error = nil;
if ([self.managedObjectContext save:&error] == NO)
{
NSLog(#"CANNOT SAVE: %# %#", error, [error localizedDescription]);
}
else {
NSLog(#"SUCCES, go check the sqlite file");
}
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"GetAlbums"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSError* error;
NSArray *fetchedRecords = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSLog(#"Fetch Records %#",fetchedRecords);
Even if, as suggested by walle84, you can implement the methods of UITableViewDelegate to display data in your table view, the right way is to use a UITableView in combination with NSFetchedResultsController. The latter is, as written in the doc, used
to efficiently manage the results returned from a Core Data fetch request to provide data for a UITableView object.
I put the emphasis on efficiently since it provides a lot of pros. For example, it allows lazy loading of data, it allows to track changes for a specific entity (implementing NSFetchedResultsControllerDelegate), etc.
Where to start? There are a lot of tuts. Just google them and you will find a good point to start.
Core Data From Scratch
How To Use NSFetchedResultsController
You use below methods of table view so show your data.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [self.arrayOfyourData count];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"CellIdentifier";
//you could use default cell or ur own custom cell.
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// Now here you could pare your self.arrayOfyourData to fetch and populate your cell.
return cell;
}

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;
}

Assigning different colors to cells depending on which selection a user makes (using core data)

I have a working app so far which basically consists of the following:
Using Core Data, I have 1 Table View Controller with an Add Button which modally calls up a new View Controller prompting the user to add text into three fields. There is also a selection field where the user has to choose between "bought" and "sold". When the user clicks save, the entry is added to the table view controller as a subtitle cell with the information filled in. It works well right now without the bought and sold aspect.
What I would like to do is simply change the color of the table view cell to be green for sold and red for bought. So when a user goes to add the information, they fill in the required fields and also choose bought or sold and then when clicking save, the table view cell displays either the green or red for each entry.
I am adding the tableView datasource and delegate methods here in the current TableViewController. With this, I am basically looking into a "Transaction" entity and fetching relationships to other Entities. The "Status" (Bought/Sold) is also in a related Entity to the Transaction Entity, in it's own entity called Purchase. So Transaction has a relationship called status.action (action being the inverse attribute to the Transaction).
Here's the code so far from the TableView:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.transactions.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Persons";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSManagedObject *transaction = [self.transactions objectAtIndex:indexPath.row];
[cell.textLabel setText:[NSString stringWithFormat:#"%# %#", [transaction valueForKeyPath:#"whoBy.name"], [transaction valueForKeyPath:#"gifting.amount"]]];
[cell.detailTextLabel setText:[NSString stringWithFormat:#"%#", [transaction valueForKeyPath:#"occasion.title"]]];
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
[self.managedObjectContext deleteObject:[self.transactions objectAtIndex:indexPath.row]];
[self.transactions removeObjectAtIndex:indexPath.row];
[self.tableView reloadData];
NSError *error = nil;
if (![self.managedObjectContext save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
[self.tableView reloadData];
}
}
}
The code from the Modal that actually goes ahead and allows the user to add the entries text (as well as select the Bought/Sold which is not implemented yet) looks like this:
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *transaction = [NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:context];
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
NSManagedObject *occasionEvent = [NSEntityDescription insertNewObjectForEntityForName:#"Occasion" inManagedObjectContext:context];
NSManagedObject *amountType = [NSEntityDescription insertNewObjectForEntityForName:#"Gift" inManagedObjectContext:context];
[person setValue:self.nameTextField.text forKey:#"name"];
[occasionEvent setValue:self.occasionTextField.text forKey:#"title"];
[amountType setValue:self.amountTextField.text forKey:#"amount"];
[transaction setValue:person forKey:#"whoBy"];
[transaction setValue:occasionEvent forKey:#"occasion"];
[transaction setValue:amountType forKey:#"gifting"];
Any help would be appreciated.
Thanks
Rather than trying to do this manually, all you need to do is read the state of the cell's object from the core data store and set the background colour appropriately.
I would also recommend using an NSFetchedResultsController as you datasource. It's designed to work well with Core Data and tableviews, and if you set up the delegates properly, it will even respond to changes in the model without any intervention from you.
edit
Following on from the comments you've added, I can see what the problem is.
Core Data stores objects not values. So when you try and put values into a core data store you turn them into objects first. And when you get the object out of core data you need to translate it into the value you want.
You can see this in what you are doing yourself when you add objects to your managed object:
[transaction setValue:#(self.wasOptions.selectedSegmentIndex == 0) forKey:#"wasReceived"];
This expression returns a BOOL
self.wasOptions.selectedSegmentIndex == 0
A BOOl is a value and can't be stored into Core Data, so you turn it into an NSNumber representation with:
#(self.wasOptions.selectedSegmentIndex == 0)
So you now have an NSNumber object representing the BOOL value of YES or NO stored in core data.
When you try and get the value for your key:
[transaction valueForKey:#"wasReceived"]
This is returning the NSNumber representation not the BOOL value it represents
In order to get the actual BOOL, use a convenient method from NSNumber boolValue. So to fix your problem replace this expression:
[transaction valueForKey:#"wasReceived"]
with this expression:
[[transaction valueForKey:#"wasReceived"] boolValue]
which returns a proper BOOL value that your conditional can operate on.

NSFetchedResultsController from multiple entities and updating model

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];
...
}

Resources