iOS Core Data example - CRUD operations

Core Data is powerful framework, standard within the iOS SDK that simplifies the interaction with the persistent data store in an object-oriented way and the programmer doesn't have to worry about the low level interaction with the underlying database, basically simplifies the crud operations that you generally deal with when using SQL with any relational database. In core data the most important object is the managed object context (MOC). The managed object context is our gateway to the database tables also know as the managed object model thru a persistent store coordinator.

iOS Core Data example - Table view iOS Core Data example - CRUD operations iOS Core Data example - NSFetchedResultsController
Here are the most important terminology that one must know for the core data framework
  • Managed Object Model
    • Model Representation of a database table in object-oriented way.
  • Managed Object
    • The one specific object inside a model. Kind of a single row in a given database table.
  • Persistent Store Coordinator
    • The bridge that is responsible for managing the connection between managed object context and actual data stores.
  • Managed Object Context
    • The gateway for your program logic to access the managed object model. This will help you add, delete, update and read records basically the managed objects from the persistant store.
In this example we will use core data to create a simple employee maintenance program. It starts of with a table view that will display all current employees using a NSFetchedResultsController which efficiently manages the results returned from a Core Data fetch request to provide data for a UITableView object. From that screen we can add an employee and also edit if we tap on a specific employee record. Please go thru the example and if there is any confusion I would suggest reading the apple documentation about Core Data Basics, here is the link for that.

Core data project setup

When you create the project make sure that you check the box "Use Core Data" as shown in the picture
iOS core data project
After the project has been created, look for the file with the extension of xcdatamodeld in your application bundle in Xcode. Click on it to open the editor. Add an entity named Employee and some attributes such as name, age and position to the entity as shown in picture. An entity is same as your database table and attributes are the table columns.
iOS core data entity and attributes
You can also choose to click on the data model inspector after you click on a given attribute to changes some properties such as if the attribute is mandatory or optional.
iOS entity attribute data model view
Now its time to create the object representation of the Employee entity. Select the Employee entity and then press Cmd+N (File -> New -> File). Select Core Data in the iOS category and then select NSManagedObject subclass from right section and press Next. Choose where you would like to create the new files in your project and press Create.
iOS create NSManagedObject subclass from entity
That's all for the core data setup. Now you should have the two files named Employee.h and Employee.m

Interface file for Employee data - Employee.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>


@interface Employee : NSManagedObject

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSString * position;
@property (nonatomic, retain) NSNumber * age;

@end

Implementation file for the Employee data - Employee.m

#import "Employee.h"

@implementation Employee

@dynamic name;
@dynamic position;
@dynamic age;

@end

Interface file for the App Delegate - EmployeeDataAppDelegate.h

#import <UIKit/UIKit.h>
#import "DisplayViewController.h"

@interface EmployeeDataAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

@property (strong, nonatomic) UINavigationController *navigationController;
@property (strong, nonatomic) DisplayViewController *displayViewController;

- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;

@end

Implementation file for the App Delegate - EmployeeDataAppDelegate.m

#import "EmployeeDataAppDelegate.h"

@implementation EmployeeDataAppDelegate

@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;

@synthesize navigationController;
@synthesize displayViewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    
    //create the navigation controller and add the controllers view to the window
    navigationController = [[UINavigationController alloc] init];
    [self.window addSubview:[self.navigationController view]];
    
    //check if the display viewcontroller eixsts, otherwise create it
    if(self.displayViewController == nil)
    {
        DisplayViewController *inputView = [[DisplayViewController alloc] init];
        self.displayViewController = inputView;
    }
    
    //push the display viewcontroller into the navigation view controller stack
    [self.navigationController pushViewController:self.displayViewController animated:YES];
    
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;

}


- (void)saveContext
{
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        } 
    }
}

#pragma mark - Core Data stack

// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }
    
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"EmployeeData" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"EmployeeData.sqlite"];
    
    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }    
    
    return _persistentStoreCoordinator;
}

#pragma mark - Application's Documents directory

// Returns the URL to the application's Documents directory.
- (NSURL *)applicationDocumentsDirectory
{
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

@end

Interface file for the Employee Display - DisplayViewController.h

#import <UIKit/UIKit.h>
#import "EditViewController.h"

@interface DisplayViewController : UIViewController
                <UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate>

@property (weak, nonatomic) NSManagedObjectContext *managedObjectContext;

@property (strong, nonatomic) EditViewController *editViewController;
@property (nonatomic, strong) UITableView *myTableView;
@property (nonatomic, strong) NSFetchedResultsController *employeeFRC;

@end

Implementation file for the Employee Display - DisplayViewController.m

#import "DisplayViewController.h"
#import "EmployeeDataAppDelegate.h"

@interface DisplayViewController ()

@end

@implementation DisplayViewController

@synthesize managedObjectContext;
@synthesize editViewController;
@synthesize myTableView;
@synthesize employeeFRC;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        
        ////get the application delegate and its managed object context
        if(self.managedObjectContext == nil){
            EmployeeDataAppDelegate *myAppDelegate = (EmployeeDataAppDelegate *)
                                                        [[UIApplication sharedApplication] delegate];
            self.managedObjectContext = myAppDelegate.managedObjectContext;
        }
        
        //Create the fetch request 
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        
        //create reference to the Employee entity
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Employee"
                                                  inManagedObjectContext:[self managedObjectContext]];
        
        //in what order you want your data to be fetched
        NSSortDescriptor *nameSort = [[NSSortDescriptor alloc] initWithKey:@"name"
                                                                 ascending:YES];
        NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: nameSort, nil];
        fetchRequest.sortDescriptors = sortDescriptors;
        
        //tell the request that we want to read the contents of the Employee entity
        [fetchRequest setEntity:entity];
        
        //initialize a fetched results controller to efficiently manage the results
        //returned from a Core Data fetch request
        self.employeeFRC = [[NSFetchedResultsController alloc]
                            initWithFetchRequest:fetchRequest
                            managedObjectContext:[self managedObjectContext]
                            sectionNameKeyPath:nil
                            cacheName:nil];
        //notify the view controller when the fetched results change
        self.employeeFRC.delegate = self;
        
        NSError *fetchingError = nil;
        //perform the fetch request
        if ([self.employeeFRC performFetch:&fetchingError]){
            NSLog(@"Successfully fetched data from Employee entity");
        }
        else {
            NSLog(@"Failed to fetch any data from the Employee entity");
        }
        
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
 //set the title of the navigation view
    [self.navigationItem setTitle:@"Employee List"];
    
    //create a submit button in the navigation bar
    UIBarButtonItem *myButton = [[UIBarButtonItem alloc]
                                 initWithTitle:@"Add"
                                 style:UIBarButtonItemStylePlain
                                 target:self
                                 action:@selector(addEmployee:)];
    [self.navigationItem setRightBarButtonItem:myButton];
   
    //create the table view, assign the data source and delegate
    self.myTableView = [[UITableView alloc] initWithFrame:self.view.bounds
                                                    style:UITableViewStyleGrouped];
    self.myTableView.delegate = self;
    self.myTableView.dataSource = self;
    [self.view addSubview:self.myTableView];
    
}

//notify the receiver that the fetched results controller has completed processing
//of one or more changes due to an add, remove, move, or update
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
    [self.myTableView reloadData];
}

//the number of rows in a given section of a table view
- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section{
    
    id <NSFetchedResultsSectionInfo> sectionInfo = [self.employeeFRC.sections objectAtIndex:section];
    return [sectionInfo numberOfObjects];
    
}

//insert in a particular location of the table view for a given section/row location
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    UITableViewCell *myCell = nil;
    static NSString *EmployeeCell = @"EmployeeCell";
    
    //create a reusable table-view cell object located by its identifier
    myCell = [tableView dequeueReusableCellWithIdentifier:EmployeeCell];
    if (myCell == nil){
        myCell =
        [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                            reuseIdentifier:EmployeeCell];
        myCell.selectionStyle = UITableViewCellSelectionStyleNone;
    }
    
    //get the employee object at the given index path in the fetch results
    Employee *employee = [self.employeeFRC objectAtIndexPath:indexPath];
    
    //display text for the cell view
    myCell.textLabel.text = [NSString stringWithFormat:@"%@", employee.name];
    
    //set the accessory view to be a clickable button
    myCell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
    
    return myCell;
}

//informs the delegate that the user tapped the accessory view associated with a given row
- (void) tableView:(UITableView *)tableView
    accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath{
    
    if ([tableView isEqual:self.myTableView]){
        NSLog(@"%@",
              [NSString stringWithFormat:@"Cell %ld accessory button in Section %ld is tapped",
               (long)indexPath.row, (long)indexPath.section]);
        
        //let go ahead and allow edit of the employee information
        [self editEmployee:indexPath];
    }
}

- (void) editEmployee:(NSIndexPath *)indexPath {
    
    //if the edit view controller doesn't exists create it
    if(self.editViewController == nil){
        EditViewController *editView = [[EditViewController alloc] init];
        self.editViewController = editView;
    }
    
    //pass the employee id to the edit view controller
    Employee *employee = [self.employeeFRC objectAtIndexPath:indexPath];
    NSManagedObjectID *employeeID = [employee objectID];
    [self.editViewController setEmployeeID:employeeID];
    
    //pass the NSManagedObjectContext
    self.editViewController.managedObjectContext = self.managedObjectContext;
    
    //tell the navigation controller to push a new view into the stack
    [self.navigationController pushViewController:self.editViewController animated:YES];
    
}


- (void) addEmployee:(id)sender {
    
    //get reference to the button that requested the action
    UIBarButtonItem *myButton = (UIBarButtonItem *)sender;
    
    //check which button it is, if you have more than one button on the screen
    //you must check before taking necessary action
    if([myButton.title isEqualToString:@"Add"]){
        NSLog(@"Clicked on the bar button");
        
        //if the edit view controller doesn't exists create it
        if(self.editViewController == nil){
            EditViewController *editView = [[EditViewController alloc] init];
            self.editViewController = editView;
        }
        
        //set the employee id to ZERO when creating a new employee
        [self.editViewController setEmployeeID:0];
        
        //pass the NSManagedObjectContext
        self.editViewController.managedObjectContext = self.managedObjectContext;
        
        //tell the navigation controller to push a new view into the stack
        [self.navigationController pushViewController:self.editViewController animated:YES];
        
    }
    
}


- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

Interface file for the Employee Edit - EditViewController.h

#import <UIKit/UIKit.h>
#import "Employee.h"

@interface EditViewController : UIViewController

@property (weak, nonatomic) NSManagedObjectContext *managedObjectContext;

@property (nonatomic, strong) UITextField *name;
@property (nonatomic, strong) UITextField *position;
@property (nonatomic, strong) UITextField *age;

@property (nonatomic) NSManagedObjectID *employeeID;

@end

Implementation file for the Employee Edit - EditViewController.m

#import "EditViewController.h"

@interface EditViewController ()

@end

@implementation EditViewController

@synthesize name, position, age;
@synthesize managedObjectContext;
@synthesize employeeID;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
 //set the title of the navigation view
    [self.navigationItem setTitle:@"Employee Info"];
    
    //create screen layout
    [self screenLayout];
    
    //create a submit button in the navigation bar
    UIBarButtonItem *myButton = [[UIBarButtonItem alloc]
                                 initWithTitle:@"Save"
                                 style:UIBarButtonItemStylePlain
                                 target:self
                                 action:@selector(saveEmployee:)];
    [self.navigationItem setRightBarButtonItem:myButton];
    
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:YES];
    
    //if we have the employee id then its in an edit mode, load the existing values
    if(self.employeeID != nil){
        
        NSError *error = nil;
        //get the employee object based on the id that was passed
        Employee *employee = (Employee *)[self.managedObjectContext
                                          existingObjectWithID:self.employeeID
                                          error:&error];
        
        //set the data from the employee object into the text view
        name.text = employee.name;
        position.text = employee.position;
        age.text = [NSString stringWithFormat:@"%@", employee.age];
        
    }
    //add mode clear our text view fields
    else {
        name.text = @"";
        position.text = @"";
        age.text = @"";
    }
    
    
}

- (void) screenLayout {
    
    CGRect myFrame = CGRectMake(10.0f, 10.0f, 100.0f, 30.0f);
    UILabel *myLabel = [[UILabel alloc] initWithFrame:myFrame];
    myLabel.font = [UIFont boldSystemFontOfSize:16.0f];
    myLabel.textAlignment =  NSTextAlignmentRight;
    myLabel.text = @"Name:";
    [self.view addSubview:myLabel];
    
    myFrame.origin.y += 35.0f;
    myLabel = [[UILabel alloc] initWithFrame:myFrame];
    myLabel.font = [UIFont boldSystemFontOfSize:16.0f];
    myLabel.textAlignment =  NSTextAlignmentRight;
    myLabel.text = @"Position:";
    [self.view addSubview:myLabel];
    
    myFrame.origin.y += 35.0f;
    myLabel = [[UILabel alloc] initWithFrame:myFrame];
    myLabel.font = [UIFont boldSystemFontOfSize:16.0f];
    myLabel.textAlignment =  NSTextAlignmentRight;
    myLabel.text = @"Age:";
    [self.view addSubview:myLabel];
    
    myFrame = CGRectMake(115.0f, 10.0f, 200.0f, 30.0f);
    name = [[UITextField alloc] init];
    [name setFrame:myFrame];
    [name setBorderStyle: UITextBorderStyleRoundedRect];
    [self.view addSubview:name];
    
    myFrame.origin.y += 35.0f;
    position = [[UITextField alloc] init];
    [position setFrame:myFrame];
    [position setBorderStyle: UITextBorderStyleRoundedRect];
    [self.view addSubview:position];
    
    myFrame.origin.y += 35.0f;
    age = [[UITextField alloc] init];
    [age setFrame:myFrame];
    [age setBorderStyle: UITextBorderStyleRoundedRect];
    [self.view addSubview:age];
    
    UIImage *delete = [UIImage imageNamed:@"delete.png"];
    UIButton *myButton = [UIButton buttonWithType:UIButtonTypeCustom];
    myButton.frame = CGRectMake(10.0f, 10.0f, 35.0f, 35.0f);
    [myButton setImage:delete forState:UIControlStateNormal];
    [myButton setTitle:@"Delete" forState:UIControlStateNormal];
    myButton.titleLabel.hidden = TRUE;
    [myButton addTarget:self
                 action:@selector(deleteEmployee:)
       forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:myButton];
    
}

- (void) saveEmployee:(id)sender {
    
    //get reference to the button that requested the action
    UIBarButtonItem *myButton = (UIBarButtonItem *)sender;
    NSLog(@"Clicked on the %@ button",myButton.title);
    
    //check which button it is, if you have more than one button on the screen
    //you must check before taking necessary action
    if([myButton.title isEqualToString:@"Save"]){
        
        //no object id then we have to create a new employee
        if(self.employeeID == nil){
        [self createNewEmployeeWithName:self.name.text
                               position:self.position.text
                    age:[self.age.text intValue]];
        }
        //let update the employee as it already exists
        else {
            [self updateEmployeeWithName:self.name.text
                                position:self.position.text
                                    age:[self.age.text intValue]];
        }
        
        [self.navigationController popViewControllerAnimated:YES];
        
    }
    
}

//delete the employee
- (void) deleteEmployee:(id)sender {
    
    //get reference to the button that requested the action
    UIButton *myButton = (UIButton *)sender;
    NSLog(@"Clicked on the %@ button",myButton.currentTitle);
    
    if([myButton.currentTitle isEqualToString:@"Delete"]){
        
        if(self.employeeID != nil){
            
            NSError *error = nil;
            Employee *employee = (Employee *)[self.managedObjectContext
                                              existingObjectWithID:self.employeeID
                                              error:&error];
            
            if (employee == nil){
                NSLog(@"Failed to get the employee object");
                return;
            }
            
            
            [self.managedObjectContext deleteObject:employee];
            
            NSError *savingError = nil;
            if ([self.managedObjectContext save:&savingError]){
                NSLog(@"Employee data was deleted");
            }
            else {
                NSLog(@"Failed to delete the employee, Error = %@", savingError);
            }
            
        }
        
        [self.navigationController popViewControllerAnimated:YES];
        
    }
    
}

- (BOOL) updateEmployeeWithName:(NSString *)paramName
                          position:(NSString *)paramPosition
                               age:(NSUInteger)paramAge{
    
    BOOL success = NO;
    if ([paramName length] == 0){
        NSLog(@"Name is mandatory.");
        return NO;
    }
    
    NSError *error = nil;
    Employee *employee = (Employee *)[self.managedObjectContext
                                      existingObjectWithID:self.employeeID
                                      error:&error];
    
    if (employee == nil){
        NSLog(@"Failed to get the employee object");
        return NO;
    }
    
    employee.name = paramName;
    employee.position = paramPosition;
    employee.age = [NSNumber numberWithUnsignedInteger:paramAge];
    
    NSError *savingError = nil;
    
    if ([self.managedObjectContext save:&savingError]){
        NSLog(@"Employee data was updated");
        return YES;
    }
    else {
        NSLog(@"Failed to update the employee, Error = %@", savingError);
    }
    return success;
}


- (BOOL) createNewEmployeeWithName:(NSString *)paramName
                        position:(NSString *)paramPosition
                             age:(NSUInteger)paramAge {
    BOOL success = NO;
    if ([paramName length] == 0){
        NSLog(@"Name is mandatory.");
        return NO;
    }
        
    Employee *newEmployee = [NSEntityDescription
                             insertNewObjectForEntityForName:@"Employee"
                             inManagedObjectContext:self.managedObjectContext];
        
    if (newEmployee == nil){
        NSLog(@"Failed to create the new employee");
        return NO;
    }
        
    newEmployee.name = paramName;
    newEmployee.position = paramPosition;
    newEmployee.age = [NSNumber numberWithUnsignedInteger:paramAge];
    
    NSError *savingError = nil;
    
    if ([self.managedObjectContext save:&savingError]){
        NSLog(@"New employee was created");
        return YES;
    }
    else {
        NSLog(@"Failed to save the new employee, Error = %@", savingError);
    }
    return success;
}



- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

Related Articles

No comments:

Post a Comment

NO JUNK, Please try to keep this clean and related to the topic at hand.
Comments are for users to ask questions, collaborate or improve on existing.