ArtsAutosBooksBusinessEducationEntertainmentFamilyFashionFoodGamesGenderHealthHolidaysHomeHubPagesPersonal FinancePetsPoliticsReligionSportsTechnologyTravel

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

Updated on November 23, 2016
klanguedoc profile image

Kevin is Software Developer with 20 years experience designing and building business intelligence and system integration solutions.

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.

Comments

    0 of 8192 characters used
    Post Comment

    • klanguedoc profile imageAUTHOR

      Kevin Languedoc 

      4 years ago from Canada

      ok it is fixed

    • klanguedoc profile imageAUTHOR

      Kevin Languedoc 

      4 years ago from Canada

      Ok I will right away

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

      Kevin

    • profile image

      ZAP 

      4 years ago

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

    • profile image

      ZAP 

      4 years ago

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

    • klanguedoc profile imageAUTHOR

      Kevin Languedoc 

      4 years ago from Canada

      check out www.iosdev101.com under downloads

    • profile image

      Rita roy 

      4 years ago

      Please give me the zip project

    • klanguedoc profile imageAUTHOR

      Kevin Languedoc 

      4 years ago from Canada

      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

    • profile image

      adam 

      4 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?

    • profile image

      adam 

      4 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?

    • klanguedoc profile imageAUTHOR

      Kevin Languedoc 

      5 years ago from Canada

      Juan,

      No I didn't use that flag

    • profile image

      Juan 

      5 years ago

      Does this tutorial make use of:

      setAllowsExternalBinaryDataStorage: flag?

    • profile image

      badri vishwakarma 

      6 years ago

      not so good.

    • klanguedoc profile imageAUTHOR

      Kevin Languedoc 

      6 years ago from Canada

      Sorry no, not yet anyway :)

    • profile image

      mikeydcarroll67 

      6 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 imageAUTHOR

      Kevin Languedoc 

      6 years ago from Canada

      I am glad I was able to help.

    • profile image

      pengweisi 

      6 years ago

      This is exactly what I needed thank you so much.

    working

    This website uses cookies

    As a user in the EEA, your approval is needed on a few things. To provide a better website experience, hubpages.com uses cookies (and other similar technologies) and may collect, process, and share personal data. Please choose which areas of our service you consent to our doing so.

    For more information on managing or withdrawing consents and how we handle data, visit our Privacy Policy at: https://hubpages.com/privacy-policy#gdpr

    Show Details
    Necessary
    HubPages Device IDThis is used to identify particular browsers or devices when the access the service, and is used for security reasons.
    LoginThis is necessary to sign in to the HubPages Service.
    Google RecaptchaThis is used to prevent bots and spam. (Privacy Policy)
    AkismetThis is used to detect comment spam. (Privacy Policy)
    HubPages Google AnalyticsThis is used to provide data on traffic to our website, all personally identifyable data is anonymized. (Privacy Policy)
    HubPages Traffic PixelThis is used to collect data on traffic to articles and other pages on our site. Unless you are signed in to a HubPages account, all personally identifiable information is anonymized.
    Amazon Web ServicesThis is a cloud services platform that we used to host our service. (Privacy Policy)
    CloudflareThis is a cloud CDN service that we use to efficiently deliver files required for our service to operate such as javascript, cascading style sheets, images, and videos. (Privacy Policy)
    Google Hosted LibrariesJavascript software libraries such as jQuery are loaded at endpoints on the googleapis.com or gstatic.com domains, for performance and efficiency reasons. (Privacy Policy)
    Features
    Google Custom SearchThis is feature allows you to search the site. (Privacy Policy)
    Google MapsSome articles have Google Maps embedded in them. (Privacy Policy)
    Google ChartsThis is used to display charts and graphs on articles and the author center. (Privacy Policy)
    Google AdSense Host APIThis service allows you to sign up for or associate a Google AdSense account with HubPages, so that you can earn money from ads on your articles. No data is shared unless you engage with this feature. (Privacy Policy)
    Google YouTubeSome articles have YouTube videos embedded in them. (Privacy Policy)
    VimeoSome articles have Vimeo videos embedded in them. (Privacy Policy)
    PaypalThis is used for a registered author who enrolls in the HubPages Earnings program and requests to be paid via PayPal. No data is shared with Paypal unless you engage with this feature. (Privacy Policy)
    Facebook LoginYou can use this to streamline signing up for, or signing in to your Hubpages account. No data is shared with Facebook unless you engage with this feature. (Privacy Policy)
    MavenThis supports the Maven widget and search functionality. (Privacy Policy)
    Marketing
    Google AdSenseThis is an ad network. (Privacy Policy)
    Google DoubleClickGoogle provides ad serving technology and runs an ad network. (Privacy Policy)
    Index ExchangeThis is an ad network. (Privacy Policy)
    SovrnThis is an ad network. (Privacy Policy)
    Facebook AdsThis is an ad network. (Privacy Policy)
    Amazon Unified Ad MarketplaceThis is an ad network. (Privacy Policy)
    AppNexusThis is an ad network. (Privacy Policy)
    OpenxThis is an ad network. (Privacy Policy)
    Rubicon ProjectThis is an ad network. (Privacy Policy)
    TripleLiftThis is an ad network. (Privacy Policy)
    Say MediaWe partner with Say Media to deliver ad campaigns on our sites. (Privacy Policy)
    Remarketing PixelsWe may use remarketing pixels from advertising networks such as Google AdWords, Bing Ads, and Facebook in order to advertise the HubPages Service to people that have visited our sites.
    Conversion Tracking PixelsWe may use conversion tracking pixels from advertising networks such as Google AdWords, Bing Ads, and Facebook in order to identify when an advertisement has successfully resulted in the desired action, such as signing up for the HubPages Service or publishing an article on the HubPages Service.
    Statistics
    Author Google AnalyticsThis is used to provide traffic data and reports to the authors of articles on the HubPages Service. (Privacy Policy)
    ComscoreComScore is a media measurement and analytics company providing marketing data and analytics to enterprises, media and advertising agencies, and publishers. Non-consent will result in ComScore only processing obfuscated personal data. (Privacy Policy)
    Amazon Tracking PixelSome articles display amazon products as part of the Amazon Affiliate program, this pixel provides traffic statistics for those products (Privacy Policy)