report

How To Store and Display Images in a Core Data iPhone App

Core Data is the primary means to store persistent data in an iPhone, iPad, or iPod Touch application. Core Data is an ER (Entity Relationship) tool, an ORM (Object Relational Mapping) tool and a wrapper for a SQLite storage facility all rolled into one. Core Data uses Entities to represent the tables in SQLite and Attributes to represent the columns. You can use Predicates to build queries and build relationships between your Entities using Relationships.

This tutorial builds on the first tutorial: IOS 5 | iPhone | iPad | Tutorial on Developing Core Data Applications for Beginners to create an app that stores jpeg images that are located in the Resources folder and stores them in Core Data for display in an UITableView. The app will also show how to retrieve the stored data and display the image, path and filename in a detail view controller.

Here are some screenshots of the finished app:

Figure 1 - Running App
Figure 1 - Running App

Project Overview and Setup


The application that will be built for this tutorial will scan the Resource directory at startup (this is not the best way for a production app but serves well for simplicity and demonstration purposes) and store the filename of each file, the full path of image file and most importantly the app will store the binary representation of each jpg file in CoreData’s Persistent Store (i.e. SQLite). The app will display the filenames of each file in the UITableView and allow the user delete files from Core Data. Finally the filename, path and image will be retrieved from Core Data when displayed in a detail view controller based on the user’s selection.

For this tutorial the best template to use is the Master-Detail Template for iPhones because it provides the possibility of adding Core Data capability to the project. This is important as the template will add all the necessary code to get you started. Make sure to select iPhone as the target device and to select Core Data.

Configure the Data Model


Once the project is created, select the xcdatamodeld file. This is the data model file where you can define your Entities, Attributes, Relationships and Predicate queries that will be stored in SQLite database. By default the template adds an Event Entity and timestamp attribute. You can delete these or rename the Event to Images and rename timestamp to img and change the type to binary data. Also add a name attribute with a string type and finally a urlPath attribute also with a string type.

For this tutorial we don’t need any relationships or predicates. Your data model should resemble the screenshot below in figure 3.

Figure 3 - The Data Model in Core Data
Figure 3 - The Data Model in Core Data

Add the Images


For the sake of this tutorial, I chose to add five jpg images but you can choose your own. If you want, you can create a new Group, called images to store the image files or you can copy the files directly into the Resource folder of the project. Copy these into the project using the context menu by right clicking on the project root and select “Add files to …. project”.

Setup the Master View Controller - fetchedResultsController


You can take a look in the AppDelegate files but don’t change anything as the template provided all the necessary coding to create the NSManagedObjectContext, NSManagedObjectModel and NSPersistentStoreCoordinator for you.

Most of the changes that we will need to do is in the MasterViewController files. First open the header file and add two methods: getPath and getImageBinary. The first will capture the full path of each image file and the second will get the binary representation of each image file as a NSData type.

MasterViewController - Header Modifications

- (void)insertNewObject:(NSString *)fileName;
-(NSString *)getPath:(NSString *)fileName;
-(NSData *)getImageBinary:(NSString *)fileName;

insertNewObject was provided by the template but for this tutorial, change parameter type from id to NSString* and the parameter name to fileName. The getPath method will get the full path where the files are located. These values will be stored in the urlPath attribute in CoreData. Next, the getImageBinary will get the binary representation of each image file and return it as a NSData object to be stored in CoreData as Binary Data.

In the implementation file, change the “Event” Entity references to “Images” in the fetchedResultsController method also change the sort descriptor from “timeStamp” to “name”. The method body for the fetchedResultsController should look the code listing below.

Master View Controller - fetchedResultsController

- (NSFetchedResultsController *)fetchedResultsController
{
    if (__fetchedResultsController != nil) {
        return __fetchedResultsController;
    }
    
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Images" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
    
    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];
    
    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO];
    NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
    
    [fetchRequest setSortDescriptors:sortDescriptors];
    
    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Master"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;
    
	NSError *error = nil;
	if (![self.fetchedResultsController performFetch:&error]) {
	     // 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]);
	    abort();
	}
    
    return __fetchedResultsController;
} 

Setup the Master View Controller - configureCell


Likewise change “valueForKey” value to “name” in the configureCell method so that the fileName of each stored object will be displayed in the UITableView. The code listing below shows how the method should appear.

Master View Controller - configureCell Code

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = [[object valueForKey:@"name"] description];
}

Setup the Master View Controller - viewDidLoad


Most of the work related to gathering the image files and storing them in Core Data will be done from the viewDidLoad method. As previously mentioned this isn’t the most efficient way of doing this because:

1-Every time the app starts it will store a new copy of the image in Core Data.
2-The app doesn’t check to see if the file already exists in the database

I did the design this way to keep the design simple so to focus on the real objectives of the tutorial, which were to show how to store binary data in CoreData and also to display binary data from a CoreData storage facility.

The viewDidLoad method already has substantial amount of code from the template which can all be removed and replaced with the following code:

Master View Controller - viewDidLoad

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.navigationItem.leftBarButtonItem = self.editButtonItem;
    
    NSString *root = [[NSBundle mainBundle] bundlePath];
    NSFileManager *fileMgr = [NSFileManager defaultManager];
    NSArray *dirContents = [fileMgr contentsOfDirectoryAtPath:root error:nil];
    NSPredicate *extFilter = [NSPredicate predicateWithFormat:@"self ENDSWITH '.jpg'"];
    NSArray *onlyJPGs = [dirContents filteredArrayUsingPredicate:extFilter];
    
    for(NSString* fname in onlyJPGs)
    {
     
        [self insertNewObject:fname];

    }
 
}

Just after the “[super viewDidLoad];” line of code, the “self.navigationItem.leftBarButtonItem = self.editButtonItem;” statement creates an edit button. This is useful in order to remove any unwanted file items. This ability is especially important with this app since a new copy of file listing is added each time the app is run.

The “NSString *root = [[NSBundle mainBundle] bundlePath];” statement retrieves a handle to the Resource directory.
The following line of code, “NSFileManager *fileMgr = [NSFileManager defaultManager];” creates an instance to the file manager which we will need to retrieve the contents of the Resource directory which we will use in the next line of code.

The “NSArray *dirContents = [fileMgr contentsOfDirectoryAtPath:root error:nil]; statement uses the fileMgr object instance to retrieve the contents of the root directory using the contentsOfDirectoryAtPath method. This method returns an array of the all the filenames but for our exercise we only need the jpg files so to will need to apply a filter to remove any unwanted files.

The next two lines of code, “NSPredicate *extFilter = [NSPredicate predicateWithFormat:@"self ENDSWITH '.jpg'"];” and “NSArray *onlyJPGs = [dirContents filteredArrayUsingPredicate:extFilter];” creates a filter using the NSPredicate class then this predicate is applied to the array. For this example I am filtering on jpg files but you can change this search string to suit.

Now all that is needed to loop through the array and call the new insertNewObject method to add the files to the Core Data persistent store by passing the filename for the array to the method which we will look at next.

Modify the insertNewObject Method


This method is a modified version of the original. The only real difference from the original is that I am calling the “newManagedObject” variable to set the values of the attributes in the Core Data facility. The first instance adds the name of the file to the “name” attribute. The second call adds the path of the file using the getPath method, which we will look at in the next section. Likewise the third call of the “newManagedObject” will add the binary data to the database. The getImageBinary is listing in a separate section below.

insertNewObject Code

- (void)insertNewObject:(NSString *)fileName{
    NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
    NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
    NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
    
    [newManagedObject setValue:fileName forKey:@"name"];
    [newManagedObject setValue:[self getPath:fileName] forKey:@"urlPath"];
    [newManagedObject setValue:[self getImageBinary:fileName] forKey:@"img"];
    
    // Save the context.
    NSError *error = nil;
    if (![context save:&error]) {
        // 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]);
        abort();
    }

}

The getPath method


The method creates and instantiates an instance of the NSURL class using the “initFileURLWithPath” which takes a string parameter to return the full path of the Resource directory. We will need to append the filename by using the “stringByAppendingString” method of the NSString class.

To store the path in the Core Data urlPath attribute, we will need to assign and return the NSURL imageURL variable as a NSString. This is accomplished using the “absoluteString” property of the NSURL class.

The getPath Code

-(NSString *)getPath:(NSString *)fileName{
    NSString *root = [[NSBundle mainBundle] bundlePath];
    
    NSURL *imageURL = [[NSURL alloc] initFileURLWithPath:[root stringByAppendingString:[@"/" stringByAppendingString:fileName]]];
    
    [imageURL absoluteURL];
    NSString *path= [imageURL absoluteString];  
    NSLog(@"%@",path);
    return path;
}

The getImageBinary Method


The “absoluteString” works in the same fashion as the getPath method. This method uses the “initWithContentsOfFile” to retrieve the contents of the file to create an UIImage instance of the image file. To store the image in Core Data, we simply need to convert the UIImage variable to a binary format by creating an instance variable of the NSData class which represents binary data. After-which we simply return the imgData NSData variable and store the image contents in the database.

The getImageBinary Code

-(NSData *)getImageBinary:(NSString *)fileName{
    NSString *root = [[NSBundle mainBundle] bundlePath];
    NSString *filePath = [[NSString alloc] initWithString:[root stringByAppendingString:[@"/"stringByAppendingString:fileName]]];
    
    NSLog(@"%@",filePath);
    UIImage *img = [[UIImage alloc] initWithContentsOfFile:[root stringByAppendingString:[@"/"stringByAppendingString:fileName]]];
    NSData *imgData = UIImageJPEGRepresentation(img, 1.0);
    
    return imgData;
}

Retrieving and Displaying Core Data Stored Objects


To retrieve and display stored data in a detail view controller by selecting a value in the UITableView, we will need to implement the prepareForSegue method. Luckily for us, this has already been provided and no changes are needed for our example application. However if you change the segue in the storyboard, you will have to make the change here as well.

The prepareForSegue Code

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"showDetail"]) {
        
        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
        [[segue destinationViewController] setDetailItem:object];
    }
}

For the purposes of this sample app and to be able to display the retrieved image object for Core Data, we will need to make some changes to the DetailViewController’s Layout in the storyboard as the following figure depicts.

Move the existing UILabel control to the top of the UIView and add an UIImageView control from the object library. To add it, you select it and drag it onto the canvas. Next open the Assistant Editor alongside the storyboard using the clicking on the Assistant icon in the toolbar and drag a connection from the UIImageView control to the header file and provide a name in the appropriate field like “detailImage” and click connect to complete the connection. Close the editor by clicking on the Standard editor.

Figure 4 : DetailViewController Layout
Figure 4 : DetailViewController Layout

DetailViewController header & Implementation

Since the only change we made to the header file was through the storyboard by adding the connection to the UIImageView control, we don't need to make any further changes or add provide anymore explanation.

For the implementation, we will need to change the code, a bit, in the configureCell method as shown below.

First we will set the title of the detail view controller using the name attribute. The detailDescriptionLabel will display the path of the file and last but not the least, we will reference the NSData data to assign it to the UIImageView control by converting the raw data to a UIImage object using the initWithData method.

DetailViewController Implementation

In the implementation file change the configureView method to â¦.
- (void)configureView
{
    // Update the user interface for the detail item.

    if (self.detailItem) {
        [self setTitle:[[self.detailItem valueForKey:@"name"] description]];
        self.detailDescriptionLabel.text = [[self.detailItem valueForKey:@"urlPath"] description];
        self.detailImage.image = [[UIImage alloc]initWithData:[self.detailItem valueForKey:@"img"]];
    }


}

In Summary

Storing binary data in Core Data is quite easy once you understand that the binary file needs to be converted to a binary format which is one of the supported data types in core Data. The other point to contention is how to extract the raw binary data from the id object and converted to an UIImage image. The rest is pretty straightforward.

More by this Author


Comments 16 comments

pengweisi 4 years ago

This is exactly what I needed thank you so much.


klanguedoc profile image

klanguedoc 4 years ago from Canada Author

I am glad I was able to help.


mikeydcarroll67 4 years ago

Awesome! I might be able to use this in a few new apps.....do you know how to use the storekit?


klanguedoc profile image

klanguedoc 4 years ago from Canada Author

Sorry no, not yet anyway :)


badri vishwakarma 4 years ago

not so good.


Juan 3 years ago

Does this tutorial make use of:

setAllowsExternalBinaryDataStorage: flag?


klanguedoc profile image

klanguedoc 3 years ago from Canada Author

Juan,

No I didn't use that flag


adam 2 years ago

This was a lot of help thank you for making this.

The only thing I can't get to work right is when I try to add this to a split view under a tabview temple I keep getting a error in the MasterViewController.m viewdidload

self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];

its giving a sigabrt error. how would I resolve the error to make your great code work?


adam 2 years ago

I fixed the previous posted error but now I keep getting a error on the line with

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Images" inManagedObjectContext:self.managedObjectContext];

is it because it is not a master-detail temp?


klanguedoc profile image

klanguedoc 2 years ago from Canada Author

Hi Adam,

Glad to see you were able to work through some of the issues.I am also sorry that I haven't been around. I was away on a time consuming project. Anyway, did you resolve your second error and if not can you give me more details on error itself

Thanks

Kevin


Rita roy 2 years ago

Please give me the zip project


klanguedoc profile image

klanguedoc 2 years ago from Canada Author

check out www.iosdev101.com under downloads


ZAP 2 years ago

Hello klanguedoc your downloads is wrong... i think you double the crud...


ZAP 2 years ago

klanguedoc can you update your downloads?? when you click download for this topic you will download crud not for core data.


klanguedoc profile image

klanguedoc 2 years ago from Canada Author

Ok I will right away

Send me an e-mail to kevinlanguedoc@gmail.com and I will send the source code to you.

Kevin


klanguedoc profile image

klanguedoc 2 years ago from Canada Author

ok it is fixed

    Sign in or sign up and post using a HubPages Network account.

    0 of 8192 characters used
    Post Comment

    No HTML is allowed in comments, but URLs will be hyperlinked. Comments are not for promoting your articles or other sites.


    Click to Rate This Article

    Menu

    Explore