How to pass a custom property between view controller using NSManagedObject object? - ios

I'm working on a simple notes app, and I created an entity called "Note" in the data model that have only one attribute that named "content" of type nsstring.
Now I careated a Note class using editor/create NSManagedObject subclass from the data model section.
In my create notes view controller i'm saving the data to the database in the prepareForSegue method that looks like this:
#implementation NOCreateNotesViewController
- (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 (sender != self.saveButton) return;
if (self.noteText.text.length > 0) {
self.note = [[Note alloc] init];
self.note.content = self.noteText.text;
}
NSManagedObjectContext * context = [self managedObjectContext];
// creating a new managed object
NSManagedObject *newNote = [NSEntityDescription insertNewObjectForEntityForName:#"Note" inManagedObjectContext:context];
[newNote setValue:self.noteText.text forKey:#"content"];
NSError * error = nil;
if ([context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
}
But not I want to pass the object that a user wants to edit (meaning the note in the selected row from the table view) this way:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"editeNote"]) {
NSManagedObject * selectedNote = [self.notes objectAtIndex:[self.tableView indexPathForSelectedRow].row];
NOCreateNotesViewController * destination = [segue destinationViewController];
destination.note = selectedNote;
}
}
And obviously I can't pass a Note object with a pointer to NSManagedObject...this is because I want to update my Note entity if this is an editing segue instead of creating a new one.
In the tutorial I learned core data basics from they teach with just creating objects on the fly and note using a custom class for the objects with editor/create NSManagedObject subclass so i'm getting a bit confused..
Please help

NSManagedObject * selectedNote = [self.notes objectAtIndex:[self.tableView indexPathForSelectedRow].row];
wants to be:
Note * selectedNote = [self.notes objectAtIndex:[self.tableView indexPathForSelectedRow].row];
Why are you saving the data to your model in prepareForSegue:? Surely you have a Save or Done button in the view?

Related

How do I remove a single object in a cell of a UICollectionView?

I am using UICollectionView and with this method i can delete all objects that I have added to CoreData:
- (IBAction)btnDelete:(id)sender {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Are you sure?" message:#"Delete all favorites?" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil];
[alertView show];
}
- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex {
if(buttonIndex == 0){
nil;
} else if (buttonIndex == 1){
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
NSFetchRequest *execQuery =[[NSFetchRequest alloc] init];
NSEntityDescription *descOggetto = [NSEntityDescription entityForName:#"HexCode" inManagedObjectContext:context];
[execQuery setEntity:descOggetto];
NSError *error = nil;
_arr = [context executeFetchRequest:execQuery error:&error];
for (NSManagedObject *ogg in _arr){
[context deleteObject:ogg];
[_favoriteCollectionView reloadData];
}
NSError *saveError = nil;
[context save:&saveError];
// NSLog(#"Delete");
}
}
How do I delete a single object instead of all?
You haven't shown enough code for me to give you a specific answer. But the general outline is like this:
First you need to get the index path of the cell associated with the managed object you want to delete. How you do that depends on your use case:
NSIndexPath *indexPath = ...;
From there, get the managed object from your data model. If your data model is a mutable array (typical when you only have one section in your collection view):
NSManagedObject *obj = [self.myDataModel objectAtIndex:indexPath.item];
Then you delete the object and remove it from the array:
[obj.managedObjectContext deleteObject:obj];
NSError *saveError = nil
[obj.managedObjectContext save:saveError];
...// error handling
[self.myDataModel removeObject:obj];
Finally, you inform the collection view that you've deleted an item so it can update the display:
[self.collectionView deleteItemsAtIndexPaths:#[indexPath]];
Or if you don't want animation:
[self.collectionView reloadData];
The answer is a little bit different if you're using an NSFetchedResultsController. Some of the above steps would be taken care of in your NSFetchedResultsControllerDelegate implementation as outlined in the documentation. The remaining steps would look something like this:
NSIndexPath *indexPath = ...;
NSManagedObject *obj = [self.fetchedResultsController objectAtIndexPath:indexPath];
[obj.managedObjectContext deleteObject:obj];
NSError *saveError = nil
[obj.managedObjectContext save:saveError];
...// error handling

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).

Core Data Detail View with relationship

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.

DetailViewController not showing when creates new object

Quick question fellas...
The IBAction button is creating a New Object but when the navigator pushes the DetailViewController it's giving me SIGABRT.
Now I'm wondering if I just cannot use the navigation controller function there.
The following code is the one not working correctly
- (IBAction)insertNewObject
{
// Create a new instance of the entity managed by the fetched results controller.
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
Event *newEvent = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
DetailViewController *detailController = [[[DetailViewController alloc] init] autorelease];
[newEvent setValue:[NSDate date] forKey:#"timeStamp"];
// Save the context with the new object.
NSError *error = nil;
if (![context save:&error]) {
//improve this
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[detailController setEvent:newEvent];
NSLog(#"Detail Controller New Event 1 (from Master) : %#", newEvent);
[self.navigationController pushViewController:detailController animated:YES];
}
Now When I select the cell it works perfectly. So the below code works:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"selectSegue"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Event *event = (Event *)[fetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"Event on Master:: %#", event);
DetailViewController *detailvc = [[[DetailViewController alloc] init] autorelease];
detailvc.event = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"Event on Detail when created :: %#", detailvc.event);
if (event) [[segue destinationViewController] setEvent:(Event *) [self.fetchedResultsController objectAtIndexPath:indexPath]]; //was: setDetailItem:selectedObject
[event release];
[detailvc.event retain];
}
}
Feels like I'm missing something simple. But could anyone help me add the object to that DetailViewController without getting a SIGABRT ?
Thanks and Happy New Year everyone.
Your Code seems to be all fine.
For my Experience, you should check if you Set all Mandatory Attributes for the Entity. Because in some Cases the Error statement doesnt mind this error!

Passing context in iOS to use Core Data with Storyboard

I'm having some problems in passing context from the app delegate to the view controller.
I've found many tutorials on the internet, and all suggest to use the didFinishLaunchingWithOptions method to create the view controller, set the context property and push it.
My problem is that I want to use storyboard, and the view controller is created and pushed within it, and not in the app delegate.
I've tried to do this in my app delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//instantiate local context
NSManagedObjectContext *context = [self managedObjectContext];
if (!context)
{
// Handle the error.
NSLog(#"Error: Context is null");
}
//reference the view controller
helloCoreDataViewController1_001 *rootViewController = [helloCoreDataViewController1_001 alloc];
// Pass the managed object context to the view controller
rootViewController.managedObjectContext = context;
return YES;
}
and this in my view controller:
#implementation helloCoreDataViewController1_001
#synthesize name, address, phone, status, managedObjectContext;
//....
- (IBAction)saveContact
{
NSLog(#"name: %#",self.name.text);
NSLog(#"address: %#",self.address.text);
NSLog(#"phone: %#",self.phone.text);
//Save the new instance of the contact entity
Contact *contact = (Contact *)[NSEntityDescription insertNewObjectForEntityForName:#"Contacts" inManagedObjectContext:managedObjectContext];
[contact setContactName:[NSString stringWithFormat:#"%#",self.name.text]];
[contact setValue:self.address.text forKey:#"address"];
[contact setContactPhone:[NSString stringWithFormat:#"%#",self.phone.text]];
NSError *error = nil;
if (![managedObjectContext save:&error])
{
// Handle the error.
NSLog(#"error: %#", error.description);
self.status.text = #"Error: contact NOT saved";
}
else
self.status.text = #"Contact saved";
}
When I debug, I can see that in the app delegate, the context is populated correctly, and also the property in the view controller is ok.
But when my saveContact method is invoked, the context is empty.
Do you have any suggestions about this? How can I pass the context to the view controller with storyboard?
You can get the Storyboard's rootviewcontroller in didFinishLaunchingWithOptions by accessing
self.window.rootViewController
instead of allocing a new one.
So those two lines in your didFinishLaunchingWithOptions should look like this:
helloCoreDataViewController1_001 *rootViewController = (helloCoreDataViewController1_001 *)self.window.rootViewController;
rootViewController.managedObjectContext = context;
Instead of passing your managed obeject context to the view controller try to get it on the view controller from the appDelegate:
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
if (managedObjectContext == nil) {
id appDelegate = (id)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = [appDelegate managedObjectContext];
}... //finish your fetch request of course...
}
With me it worked beautifully

Resources