Search code examples
iphonecore-dataentity-relationshipsaveone-to-many

iphone, Saving data in to-many relationship, Core Data


I'm working on a small iphone app using Core Data, where I got a Person and an Image entities. The relationship between Person and Image is to-many. One person to many images.

Problem happens at save method. During saving first I need 'ADD' more than one image data, that I got from ImagePickerViewController, to Person and then 'SAVE' the Person entity to actual DB. ((in the same method))

if (managedObjectContext == nil)
{
managedObjectContext = [(CoreDataCombine01AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
//???: how to init _PersonImage?
_PersonImage = (PersonImage *)[NSEntityDescription insertNewObjectForEntityForName:@"PersonImage" inManagedObjectContext:managedObjectContext];     
//_PersonImage = [_PersonImage init];
//_PersonImage = [[_PersonImage alloc] init];
_PersonImage.originalImage = imageData;

managedObjectContext = nil;
[managedObjectContext release];

.

 if (managedObjectContext == nil)
  {
  managedObjectContext = [(CoreDataCombine01AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
person = (Person *)[NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:managedObjectContext]; 
[person addPersonToPersonImageObject:_PersonImage];//runTime error 
[_PersonImage release];

.

NSError *error = nil;
if (![person.managedObjectContext save:&error]) {
    // Handle error
    NSLog(@"Unresolved error at save %@, %@", error, [error userInfo]);
    exit(-1);  // Fail
}

then I got error saying:

"-[NSConcreteMutableData CGImage]: unrecognized selector sent to instance" 

and:

 "*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteMutableData CGImage]: unrecognized selector sent to instance".

I guessed the error came from double use of NSEntityDescription from one managedObjectContext. So I just init Person class, that derived automatically from data model and manually imported, like the commented line instead of using managedObjectContext.

It doesn't give any error but give me runtime error when I hit my save button.

When I was using one-to-one relationship there was no problem with saving so I guess using managedObjectContext for Person is right way. However, once I use to-many relationship I need save Image data to PersonImage entity. And the entity has to be initialized in a way or another.

What am I missing?


Solution

  • Alright.

    I've had a look at the code you posted on GitHub and found a few issues with your DetailView controller, which I am outlining below.

    Firstly, you were not passing your managed object context to the detail view properly, meaning that when you were trying to save your objects there was no context for them to be saved from. Think of the Managed Object Context as a "draft" of your persistent store. Any changes you make to an NSManagedObject will be tracked and kept on your context until you persist them with the [managedObjectContext save] command.

    So just to be clear, you are creating your context in your AppDelegate, then you passed a reference to it to your RootViewController with rootViewController.managedObjectContext = self.managedObjectContext

    From the RootViewController, you need to pass the managedObjectContext down to the DetailView using the same technique. So in your tableView:didSelectRowAtIndexPath: method, you should pass a reference to the context so any changes you make on that view will be tracked and can eventually be persisted:

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {    
         DetailView *detailView = [[DetailView alloc] initWithNibName:@"DetailView" bundle:nil];
         detailView.event = (Event *)[[self fetchedResultsController] objectAtIndexPath:indexPath];
         detailView.managedObjectContext = self.managedObjectContext;
    
         // ...
         [self.navigationController pushViewController:detailView animated:YES];
         [detailView release];
    }
    

    I've also updated all the other references to managedObjectContext where you were instantiating a new context object to simply point to self.managedObjectContext,i.e:

    JustString *_justString = [NSEntityDescription insertNewObjectForEntityForName:@"JustString" inManagedObjectContext:self.managedObjectContext];
    

    Once that's out of the way, there is only one more thing that was preventing you from saving the image object properly, which TechZen touched on above.

    In your DetailView controller, you have a imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ method where you transform the UIImage into NSData and then assign it to your Image entity instance.

    The problem with that is that you already have a transformer method in your object model (see Event.m) called -(id)transformedValue:(id)value.

    So basically you were trying to transform the UIImage into NSData and then passing NSData to the entity, which was actually expecting an UIImage. In this case, I'd recommend that you let your object model deal with the data transformation so in your DetailView controller, comment out the UIImage to NSData transformer code, and pass the image directly to your managed object:

    - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
        UIImage *selectedImage = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
        // Transform the image to NSData
        // ImageToDataTransformer *transformer = [[[ImageToDataTransformer alloc] init] autorelease];
        // NSData *imageData = [transformer transformedValue:selectedImage];
        Image *_image = [NSEntityDescription insertNewObjectForEntityForName:@"Image" inManagedObjectContext:self.managedObjectContext];    
        _image.justImage = selectedImage;
        [event addEventToImageObject:_image];
        [picker dismissModalViewControllerAnimated:YES];
    }
    

    After that, you should be good to go so give it a try and let us know if it solves your problems.

    CoreData can have a bit of a steep learning curve but once you 'get it' you will realise it's one of the most beautifully crafted APIs available in iOS development.

    Good luck with it and let us know how you go! Rog