Question

I am trying to save an image in core data but after I select it in the simulator, it doesn't show up in the image view? Here is a sample of the code:

- (void) viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    if (fugitive_.image != nil) {
        self.fugitiveImage.image = [UIImage imageWithData:fugitive_.image];
    }
}

- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
   fugitive.image = UIImagePNGRepresentation([info objectForKey:UIImagePickerControllerEditedImage]);
    [self dismissModalViewControllerAnimated:YES];
    [picker release];
}
Was it helpful?

Solution

First, do not store images (or any binary data) in Core Data; especially on iOS. You will get far better performance storing it on disk and then storing a reference to the file location in Core Data.

Second, your sample code does not show how you are putting the data into Core Data. Therefore it is hard to suggest a solution.

Update

I did not find a simple reference to how to do this so here is one:

Image Cache Pre iOS 5.0

To set up an image cache on disk in a pre-iOS 5.0 environment you want to first create an attribute on your entity that is a NSString. In this example we will name that attribute imageFilename. Once that is complete we will want to create a subclass of NSManagedObject for our entity so that we can implement the helper methods:

@interface MyEntity : NSManagedObject

@property (nonatomic, retain) NSString *imageFilename;
@property (nonatomic, retain) NSImage *image;

@end

We are going to let Core Data manage the imageFilename since it is defined in the model. However we are going to implement the accessors for image.

@implementation MyEntity

@dynamic imageFilename;

@synthesize image = _image;

- (void)setImage:(UIImage*)image
{
    NSString *filename = [self imageFilename];
    if (!filename) {
        filename = [[NSProcessInfo processInfo] globallyUniqueString];
        [self setImageFilename:filename];
    }

    [_image release];
    _image = [image retain];

    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    NSString *filePath = [cachePath stringByAppendingPathComponent:filename];
    NSData *data = UIImagePNGRepresentation(image);

    NSError *error = nil;
    if (![data writeToFile:filePath options:NSDataWritingAtomic error:&error]) {
        NSLog(@"Failed to write image to disk: %@\n%@", [error localizedDescription], [error userInfo]);
        return;
    }
}

The -setImage: will save the image to disk into the cache directory (note that the cache directory is not backed up and can be delete by the system in the event of a low space situation). It selects a random filename if one has not been created already.

The path is intentionally not stored because the directory of the application's sandbox can change. Therefore we only want to store the filename in Core Data and resolve the path.

We also keep an in memory reference to the image so that we are not hitting disk if the image is asked for again during this entity's lifecycle. This is the reason for the @synthesize even though we are implementing the accessors.

Note that we store the images on disk as PNG. This can be expensive (the compression routines are relatively slow) but it keeps the image in a universal format which can be useful.

- (UIImage*)image
{
    if (_image) return _image;
    NSString *filename = [self imageFilename];
    if (!filename) return nil;
    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    NSString *filePath = [cachePath stringByAppendingPathComponent:filename];

    if (![[NSFileManager defaultFileManager] fileExistsAtPath:filePath]) return nil;

    _image = [[UIImage alloc] initWithContentsOfFile:filePath];
    return _image;
}

The implementation of the -image is pretty much the reverse. We check to see if we have a filename; resolve the full path and load the image into memory and then return it to the caller.

- (void)prepareForDeletion
{
    [super prepareForDeletion];

    NSString *filename = [self imageFilename];
    if (!filename) return nil;
    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    NSString *filePath = [cachePath stringByAppendingPathComponent:filename];

    NSError *error = nil;
    if (![[NSFileManager defaultFileManager] removeItemAtPath:filePath error:&error]) {
        NSLog(@"Potential error removing on disk image: %@\n%@", [error localizedDescription], [error userInfo]);
    }
}

We want to keep our cache directory as clean as possible so that we do not create a low space situation. Therefore when the entity is going to be deleted from Core Data we want to remove the file from disk. The actual error that happens during a delete is a non-fatal issue for us. It could be an error because the file was already deleted or something else. There is no reason to completely fail for this error but it is important to log it.

- (void)willTurnIntoFault
{
    [super willTurnIntoFault];
    [_image release], _image = nil;
}

@end

Finally, we implement the -willTurnIntoFault method so that we can release our in-memory reference to the image at the end of this entity's lifecycle.

Image Cache iOS 5.0+

  1. Create a binary attribute on the entity.
  2. Turn on the "Store in External Record File" bit.
  3. There is no step three
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top