Core Data Detail View with relationship - ios

I have set up a 1 to many relationship on my core data entities. I am trying to show the detailview copy of the associated data. Currently I have the prepareforseague: method working with the original entity(Routines), however I am at a lose as to how to show the linked entity (RoutinesDetails).
FBCDRoutineViewController
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Routines"];
self.routines = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"UpdateDevice"]) {
NSManagedObject *selectedDevice = [self.routines objectAtIndex:[[self.tableView indexPathForSelectedRow] row]];
FBCDRoutineViewController *destViewController = segue.destinationViewController;
destViewController.routines = selectedDevice;
}
FBCDRoutineDetailViewController
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"RoutinesDetails"];
self.routines = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
}
- (void)viewDidLoad
{
[[self navigationController] setNavigationBarHidden:NO animated:YES];
[super viewDidLoad];
// Do any additional setup after loading the view.
if (self.routines) {
[self.testLabel setText:[self.routines valueForKey:#"routinename"]];
}
}
FBCDRoutineDetailViewController
#property (strong) NSManagedObject *routines;
This is my first time with core data and I am looking at how to show the Details entity. Am I Close to getting it working? If not can I get directed at to what I should be looking at.
Any suggestions?

If I understand your problem correctly, you want to display all RoutinesDetails objects
that are related to the Routines object passed in prepareForSegue.
Then you would declare two properties in the FBCDRoutineDetailViewController:
#property (strong) NSManagedObject *routines; // the passed object
#property (strong) NSManagedObject *routineDetails; // the displayed details objects
and fetch them like this:
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"RoutinesDetails"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"routineinfo == %#", self.routines];
[fetchRequest setPredicate:predicate];
NSError *error;
self.routineDetails = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
self.routineDetails is now the data source array for the details view.
(Remark: For displaying the result set of a Core Data request in a table view,
you might also consider to use a NSFetchedResultsController.)

I think I see several problems--mostly related to the timing of these various calls.
I believe viewDidLoad is called on your detail view before the prepareForSegue is called. So your code in viewDidLoad is trying to display data about your detail item before it has been set.
Then the code in viewDidAppear looks to be overwriting the value you set in prepareForSegue, which doesn't make sense to me (although by this time the view is already displayed and it's not going to affect the label you tried to set in viewDidLoad).
Also, executeFetchRequest: returns an NSArray, not an NSManagedObject, so assigning the result of your fetch to your NSArray property is a BAD idea.

Related

Adding data to Core Data (using tableview), delete existing ones

I'm triing to add NSManagedObject to existing data, but right after i did, my existing data disappears, and i cannot figure out why. Looking the code for like an hour couldnt help much :(
FirstTableViewController.m //DELEGATE points to AppDelegate
- (void)viewDidLoad {
[super viewDidLoad];
self.context=[DELEGATE managedObjectContext];
self.everyWish=[[NSMutableArray alloc]initWithArray:[DELEGATE collectSharedWishes]]; //collects every shared wishes from core data
self.emberek=[[NSMutableArray alloc]initWithArray:[DELEGATE collectUserNames]]; //collects every user
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:IDENTIFIER forIndexPath:indexPath];
User *emberek=[self.emberek objectAtIndex:indexPath.row];
NSMutableArray *currentUserWishes=[[NSMutableArray alloc]initWithArray:[DELEGATE collectCurrentUserSharedWishes:emberek]]; //collects the current user wishes only from core data
int completed=0;
for(SharedWishes *wish in currentUserWishes)
if([wish.wishIsPurchased isEqual:#1])
completed++;
cell.textLabel.text=emberek.userName;
cell.detailTextLabel.text=[NSString stringWithFormat:#"Wishes: %lu, purchased: %d", (unsigned long)currentUserWishes.count, completed];
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"pushToCurrentUser"]){
User *user=[self.emberek objectAtIndex:[[self.tableView indexPathForSelectedRow]row]];
SharedTableViewController *wishTVC=[segue destinationViewController];
wishTVC.user=user;
NSLog(#"%#", user);
}
}
OtherTableViewController.h
#property NSManagedObjectContext *context;
#property User *user;
OtherTableViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
self.context=[DELEGATE managedObjectContext];
self.format=[[NSDateFormatter alloc]init];
self.todayFormat=[[NSDateFormatter alloc]init];
self.results=[[NSMutableArray alloc]init];
self.wishes=[[NSMutableArray alloc]initWithArray:[DELEGATE collectCurrentUserSharedWishes:self.user]];
self.navigationItem.title=[NSString stringWithFormat:#"%#'s list", self.user.userName];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(refreshTableViewData) name:#"refreshTableViewData" object:nil];
self.refreshControl=[[UIRefreshControl alloc] init];
[self.refreshControl setTintColor:[UIColor redColor]];
[self.tableView addSubview:self.refreshControl];
[self.refreshControl addTarget:self action:#selector(pullDownToRefresh:) forControlEvents:UIControlEventValueChanged];
}
-(void)fetch:(CKRecord *)obj
{
NSError *error;
SharedWishes *wish=[NSEntityDescription insertNewObjectForEntityForName:#"SharedWishes" inManagedObjectContext:self.context];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"SharedWishes" inManagedObjectContext:self.context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"wishID==%#", obj.recordID.recordName];
[fetchRequest setPredicate:predicate];
NSArray *fetchedObjects = [self.context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil)
NSLog(#"Nem sikerult az ab-t elerni!, %#", [error localizedDescription]);
if([fetchedObjects isEqual:#[]])
{
User *currentUser=[[DELEGATE fetchUserWithID:self.user.userID] objectAtIndex:0]; //fetches the current user, which i already have, tried before using [self.user addSharedWishesObject:wish]
wish.wishName=obj[#"name"];
wish.wishPrice=obj[#"price"];
wish.wishIcon=obj[#"icon"];
wish.wishTime=obj[#"creationDate"];
wish.wishLocation=obj[#"location"];
wish.wishDescription=obj[#"description"];
wish.wishType=obj[#"type"];
wish.wishURL=obj[#"url"];
wish.wishImage=obj[#"image"];
wish.wishID=obj.recordID.recordName;
wish.wishIsPurchased=obj[#"purchased"];
wish.wishChangeTag=obj.recordChangeTag;
NSError *error;
[currentUser addSharedWishesObject:wish];
if(![self.context save:&error])
NSLog(#"Nem tudtam eltarolni az adatbazisban, oka: %#", [error localizedDescription]);
}
else
{
wish=[fetchedObjects objectAtIndex:0];
if(![wish.wishChangeTag isEqualToString:obj.recordChangeTag]){
wish.wishName=obj[#"name"];
wish.wishPrice=obj[#"price"];
wish.wishIcon=obj[#"icon"];
wish.wishTime=obj[#"creationDate"];
wish.wishLocation=obj[#"location"];
wish.wishDescription=obj[#"description"];
wish.wishType=obj[#"type"];
wish.wishURL=obj[#"url"];
wish.wishImage=obj[#"image"];
wish.wishID=obj.recordID.recordName;
wish.wishIsPurchased=obj[#"purchased"];
wish.wishChangeTag=obj.recordChangeTag;
//wish.user.userID=obj.creatorUserRecordID.recordName;
NSError *error;
if(![self.context save:&error])
NSLog(#"Nem tudtam eltarolni az adatbazisban, oka: %#", [error localizedDescription]);
}
}
}
Here is the code, it runs without an error, everything declared properly, although i didnt mention (in order to simplify my code).
After running, I have all the users, but only the last one's wishes appears, calling the NSMutableArray *currentUserWishes=[[NSMutableArray alloc]initWithArray:[DELEGATE collectCurrentUserSharedWishes:emberek]]; from FirsTableViewController. The last one has correct amount of objects, the previous ones have #"0 objects".
Saving them right in the OtherTableViewController? Any thoughts?
ps: posting more details if needed
BUG fixed:
Right after I saved the objects, I called a method, in which I compared my elelements (in my DB) to the cloud elements, and delete those which wasn't available any longer in the clouds from my persistent database. A "wrong" predicate caused the main issue because I queried all my database objects, instead of the particular user's elements

Attempt to Present whose view not in window hierarchy

I am having the following issue when presenting the UITableViewController.
List[12426:444700] Warning: Attempt to present <UserTableViewController: 0x7fe4ba5d8e50> on <UserViewController: 0x7fe4ba5b6db0> whose view is not in the window hierarchy!
Here's the part of my code
#implementation UserTableViewController
#synthesize users;
- (void)viewDidLoad {
// UserTableViewController
// Create Fetch Request in viewDidLoad
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
//Create NSManagedObjectContext instance
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entity];
[request setReturnsObjectsAsFaults:NO];
NSError *error;
NSArray *array = [[context executeFetchRequest:request error:&error]mutableCopy];
//setting users with array of fetched objects
[self setUsers:array];
NSLog(#"firstName is %#",array);
//[self presentViewController:UserTableViewController animated:YES completion:nil];
}
Really appreciate help.
You are attempting to present ([self presentViewController:animated:completion:]) UserTableViewController from within your UserViewController when your UserViewController instance is not part of the window hierarchy, e.g. on the navigation stack (has been pushed or presented itself).

save NSManagedContext in some cases only

I need to implement cancel/save behavior for the Core Data objects. I have a UITableView for which I got data from NSFetchedResultsController.
- (void) configureWithCategoryItem: (CategoryItem *) categoryItem
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:[CategoryItem entityName]];
request.predicate = [NSPredicate predicateWithFormat:#"categoryId = %#", categoryItem.categoryId];
request.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:[CategoryItem defaultContext]
sectionNameKeyPath:#"title"
cacheName:nil];
[self.fetchedResultsController performFetch: NULL];
self.layers = [self categoryLayers];
self.sectionTitle = categoryItem.title;
}
Every click on the cell in this table view
LayerItem *layerItem = [self.layers objectAtIndex:indexPath.row];
layerItem.isSelected = [NSNumber numberWithBool:YES];
And in the navigation bar I have two buttons:
Cancel - I need to dismiss controller without saving the results to the core data,
Save - I need to save results and dismiss controller
The save method now looks like this:
- (void) saveChanges
{
NSManagedObjectContext *localContext = self.fetchedResultsController.managedObjectContext;
if ([localContext hasChanges]) {
[localContext save: NULL];
[[NSNotificationCenter defaultCenter] postNotificationName:kEADatabaseWasUpdated object:nil];
}
}
Is there some instrument in Core Data which can help me to implement this behavior?
I tried to use separate NSManagedObjectContext and mergeChangesFromContextDidSaveNotification but didn't receive good result.. Waiting for your help.
- (void) cancelChanges
{
NSManagedObjectContext *localContext = self.fetchedResultsController.managedObjectContext;
[localContext rollback];
}

Update TableView after adding CoreData

I'm working on a CoreData App which displays the content of the CoreData in a TableView.
The Problem is, after adding a string the tableView is not Updating the Content of the CoreData.
i tried it with:
- (void)viewWillAppear:(BOOL)none
{
[self.tableView reloadData];
}
But no Chance, its not reloading.
Any guesses or should I post some more Code? thank you :)
So more Code :)
Saving string to CoreData:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
AppDelegate *appDelegate =
[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context =
[appDelegate managedObjectContext];
NSManagedObject *newProduct;
newProduct = [NSEntityDescription
insertNewObjectForEntityForName:#"Products"
inManagedObjectContext:context];
[newProduct setValue: _textField.text forKey:#"productName"];
NSError *error;
[context save:&error];
NSLog(#"Product saved");
}
And the Code of the TableViewController:
#implementation CartViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//Core Data Versuche
/*
Fetch existing events.
Create a fetch request for the Event entity; add a sort descriptor; then execute the fetch.
*/
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Products"];
[request setFetchBatchSize:20];
// Order the events by creation date, most recent first.
// NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"creationDate" ascending:NO];
// NSArray *sortDescriptors = #[sortDescriptor];
// [request setSortDescriptors:sortDescriptors];
// Execute the fetch.
NSError *error;
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
NSArray *fetchResult = [delegate.managedObjectContext executeFetchRequest:request error:&error];
if (fetchResult== nil) {
// 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]);
}
// Set self's events array to a mutable copy of the fetch results.
[self setCartProductsArray:[fetchResult mutableCopy]];
/*
Reload the table view if the locale changes -- look at APLEventTableViewCell.m to see how the table view cells are redisplayed.
*/
__weak UITableViewController *cell = self;
[[NSNotificationCenter defaultCenter] addObserverForName:NSCurrentLocaleDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[cell.tableView reloadData];
}];
// Ende Core Data Versuche
}
- (void)viewWillAppear:(BOOL)none
{
[self.tableView reloadData];
}
What you probably want to do is use an NSFetchedResultsController.
Make sure that when you are updating the data, you are really just updating CoreData.
When things change in CoreData and you have an NSFetchedResultsController you can subscribe to updates. Apple has great documentation on how to make this work. These updates will update your table correctly using the delegation methods. This ends up mostly being a copy and paste from the documentation.

Core Data One to Many Simple Example

Been trying to create a One to Many relationship with core data with little progress.
Just trying to make a simple core data example.
Entity A = Type:
typeName = String.
Entity B = Routine:
routineName = String.
Relationship: Type has many routines.
Ive got a few view controllers.
TypeViewController (Table view controller to list all the Types from Entity A.
AddTypeViewController (View controller to add a type to the entity through a typeTextField. This is pushed from a add button in the TypeViewController)
RoutineViewController (Table view controller which is pushed when a type is selected from TypeViewController. Lists the routines from Entity B associated with the type selected.
AddRoutineViewController (View controller to add Routine to entity B through routineNameTextField. This is pushed from a add button in the RoutineView Controller.
I started off with implementing just the first Entity and managed to get the First 2 view controllers working. (Was able to add types and view them on the first view controller.
Once i created the second entity and linked the relationship (also the inverse relationship too) the first part stopped working.
Q. Do i have to change the way data is fetched in view controller 1? (As in this view controller i am only fetching data from Entity A).
Q. Is it possible to be adding data separately? (adding type data from the view controller 2. and adding routine data from view controller 4.)
Q. When saving data in view controller 4...
- (IBAction)savePressed:(id)sender {
RoutinePlan *routinePlan = [[RoutinePlan alloc] initWithEntity:[NSEntityDescription entityForName:#"RoutinePlan" inManagedObjectContext:self.managedObjectContext]
insertIntoManagedObjectContext:self.managedObjectContext];
[routinePlan setValue:self.rNameTextField.text forKey:#"rName"];
**How to add this routine to the selected type**
NSError *savingError;
if (![self.managedObjectContext save:&savingError])NSLog(#"Error saving: %#", savingError);
}
Thank you for any help. If any more information is needed i can provide it...
Also does anyone know of a basic example out there of one to many relationships?
EDIT:
***Code snippet from TypeViewController.m
- (NSManagedObjectContext *)managedObjectContext{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([[segue identifier] isEqualToString:#"current Type Selected"]) {
NSManagedObject *selectedRoutine = [self.routineTypeArray objectAtIndex:[[self.tableView indexPathForSelectedRow] row]];
RoutinePlanViewController *destViewController = segue.destinationViewController;
destViewController.routineTypeObject = selectedRoutine;
}}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Type"];
self.routineTypeArray = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];}
**Code Snippet from AddTypeViewController.m
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;}
- (IBAction)savePressed:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *newRoutine = [NSEntityDescription insertNewObjectForEntityForName:#"Type" inManagedObjectContext:context];
[newRoutine setValue:self.rTypeTextField.text forKey:#"rType"];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil]; }
**Code Snippet from RoutineViewController.m
#synthesize routineTypeObject;
- (NSManagedObjectContext *)managedObjectContext{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"current Type Selected"]) {
AddRoutineViewController *destViewController = segue.destinationViewController;
destViewController.routineTypeObject = routineTypeObject;
}
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Routine"];
self.routineArray = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];
}
**Code Snippet from AddRoutineViewController.m
#synthesize routineTypeObject;
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (IBAction)savePressed:(id)sender {
Routine *routinePlan = [[Routine alloc] initWithEntity:[NSEntityDescription entityForName:#"Routine" inManagedObjectContext:self.managedObjectContext]
insertIntoManagedObjectContext:self.managedObjectContext];
[routinePlan setValue:self.rNameTextField.text forKey:#"rName"];
**Error
[Routine.Type = routineTypeObject];
NSError *savingError;
if (![self.managedObjectContext save:&savingError])NSLog(#"Error saving: %#", savingError);
}
Q. Do i have to change the way data is fetched in view controller 1? (As in this view controller i am only fetching data from Entity A).
No, you shouldn't need to (but you've given no information on how you currently do it or what any error is).
Q. Is it possible to be adding data separately? (adding type data from the view controller 2. and adding routine data from view controller 4.)
Yes, as long as you have a reference to the type that is being edited and the managedObjectContext to add to there is no limitation based on the view controller.
Q. When saving data in view controller 4...
You need to add the line that configures the relationship. The easiest way to do that is:
routinePlan.type = type;
(Where routinePlan has the relationship called type and you have the instance to be edited. Using the dot notation also requires that you have the managed object subclass).

Resources