分类目录归档:CoreData

IOS7-CoreDAta:DeepCopy

deepCopy


The existing _importContext will be reused as the target context for the deep copy demon- stration. Copying an object to another context isn’t as straightforward as a copy-and-paste command. To copy an object, you actually need to create a new object in the target context and then copy all the attribute values from a source object to the new object. That leaves the relationships, which can’t be copied in the same way. If you were to copy a relationship the same way you copied an attribute value, you would end up with an illegal cross-store relation- ship between the copied object and object(s) in the source store. Instead of copying a relation- ship, a deep copy needs to identify related copied objects in the target context and then establish a relationship to them from the copied object. Figure 9.2 illustrates a To-One relationship copy.

IOS7-CoreDAta案例:PrepareTVC与shopTVC,ItemVC,Unite,UnitePicker,CoreDataTVC

The NSMainQueueConcurrencyType should be used when you want the context to operate on the main thread. Any heavy work performed in this queue may slow down or even freeze the user interface. You need at least one context working in the foreground to update user interface elements.
■ The NSPrivateQueueConcurrencyType should be used when you don’t want the context to operate on the main thread. This is an ideal concurrency type for potentially heavy work, such as saving or importing data.

The sqlite file is the database file, as per usual.
■ The sqlite-wal file is the Write-Ahead Log file containing uncommitted database transactions. If you delete this file, you will lose data. If this file does not exist, there are no pending transactions waiting to be committed.
■ The sqlite-shm file is the Shared Memory file containing an index of the WAL file. This file can be regenerated automatically so you don’t need to worry about it.

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

@interface CoreDataHelper : NSObject <UIAlertViewDelegate,NSXMLParserDelegate>

@property (nonatomic, readonly) NSManagedObjectContext       *parentContext;
@property (nonatomic, readonly) NSManagedObjectContext       *context;
@property (nonatomic, readonly) NSManagedObjectContext       *importContext;

@property (nonatomic, readonly) NSManagedObjectModel         *model;
@property (nonatomic, readonly) NSPersistentStoreCoordinator *coordinator;
@property (nonatomic, readonly) NSPersistentStore            *store;

@property (nonatomic, readonly) NSManagedObjectContext       *sourceContext;
@property (nonatomic, readonly) NSPersistentStoreCoordinator *sourceCoordinator;
@property (nonatomic, readonly) NSPersistentStore            *sourceStore;

@property (nonatomic, readonly) NSPersistentStore            *iCloudStore;

@property (nonatomic, retain) MigrationVC *migrationVC;

@property (nonatomic, retain) UIAlertView *importAlertView;

@property (nonatomic, strong) NSXMLParser *parser;

@property (nonatomic, strong) NSTimer *importTimer;

- (void)setupCoreData;
- (void)saveContext;
- (void)backgroundSaveContext;
- (BOOL)reloadStore;
- (NSURL *)applicationStoresDirectory;

- (BOOL)iCloudAccountIsSignedIn;
- (void)ensureAppropriateStoreIsLoaded;
@end

CoreDataHelper.m

#import "CoreDataHelper.h"
#import "CoreDataImporter.h"
#import "Faulter.h"

@implementation CoreDataHelper
#define debug 1

#pragma mark - FILES
NSString *storeFilename = @"Grocery-Dude.sqlite";
NSString *sourceStoreFilename = @"DefaultData.sqlite";
NSString *iCloudStoreFilename = @"iCloud.sqlite";

#pragma mark - PATHS
- (NSString *)applicationDocumentsDirectory {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class,NSStringFromSelector(_cmd));
    }
    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) lastObject];
}
- (NSURL *)applicationStoresDirectory {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    NSURL *storesDirectory =
    [[NSURL fileURLWithPath:[self applicationDocumentsDirectory]]
     URLByAppendingPathComponent:@"Stores"];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:[storesDirectory path]]) {
        NSError *error = nil;
        if ([fileManager createDirectoryAtURL:storesDirectory
                  withIntermediateDirectories:YES
                                   attributes:nil
                                        error:&error]) {
            if (debug==1) {
                NSLog(@"Successfully created Stores directory");}
        }
        else {NSLog(@"FAILED to create Stores directory: %@", error);}
    }
    return storesDirectory;
}
- (NSURL *)storeURL {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[self applicationStoresDirectory]
            URLByAppendingPathComponent:storeFilename];
}
- (NSURL *)sourceStoreURL {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    return [NSURL fileURLWithPath:[[NSBundle mainBundle]
                                   pathForResource:[sourceStoreFilename stringByDeletingPathExtension]
                                   ofType:[sourceStoreFilename pathExtension]]];
}
- (NSURL *)iCloudStoreURL {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[self applicationStoresDirectory]
            URLByAppendingPathComponent:iCloudStoreFilename];
}

#pragma mark - SETUP
- (id)init {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self = [super init];
    if (!self) {return nil;}

    _model = [NSManagedObjectModel mergedModelFromBundles:nil];
    _coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_model];

    _parentContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_parentContext performBlockAndWait:^{
        [_parentContext setPersistentStoreCoordinator:_coordinator];
        [_parentContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
    }];

    _context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_context setParentContext:_parentContext];
    [_context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];

    _importContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_importContext performBlockAndWait:^{
        [_importContext setParentContext:_context];
        [_importContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
        [_importContext setUndoManager:nil]; // the default on iOS
    }];

    //_sourceCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_model];
    _sourceContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_sourceContext performBlockAndWait:^{
        [_sourceContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
        [_sourceContext setParentContext:_context];
        [_sourceContext setUndoManager:nil]; // the default on iOS
    }];

    [self listenForStoreChanges];

    return self;
}
- (void)loadStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (_store) {return;} // Don’t load store if it’s already loaded

    BOOL useMigrationManager = NO;
    if (useMigrationManager &&
        [self isMigrationNecessaryForStore:[self storeURL]]) {
        [self performBackgroundManagedMigrationForStore:[self storeURL]];
    } else {
        NSDictionary *options =
        @{
          NSMigratePersistentStoresAutomaticallyOption:@YES
          ,NSInferMappingModelAutomaticallyOption:@YES
          ,NSSQLitePragmasOption: @{@"journal_mode": @"DELETE"} // Uncomment to disable WAL journal mode
          };
        NSError *error = nil;
        _store = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                            configuration:nil
                                                      URL:[self storeURL]
                                                  options:options
                                                    error:&error];
        if (!_store) {
            NSLog(@"Failed to add store. Error: %@", error);abort();
        }
        else         {NSLog(@"Successfully added store: %@", _store);}
    }

}
- (void)loadSourceStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (_sourceStore) {return;} // Don’t load source store if it's already loaded

    NSDictionary *options =
    @{
      NSReadOnlyPersistentStoreOption:@YES
      };
    NSError *error = nil;
    _sourceStore =
    [_sourceCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                     configuration:nil
                                               URL:[self sourceStoreURL]
                                           options:options
                                             error:&error];
    if (!_sourceStore) {
        NSLog(@"Failed to add source store. Error: %@",
              error);abort();
    } else {
        NSLog(@"Successfully added source store: %@", _sourceStore);
    }
}
- (void)setupCoreData {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (!_store && !_iCloudStore) {
        if ([self iCloudEnabledByUser]) {
            NSLog(@"** Attempting to load the iCloud Store **");
            if ([self loadiCloudStore]) {
                return;
            }
        }
        NSLog(@"** Attempting to load the Local, Non-iCloud Store **");
        [self setDefaultDataStoreAsInitialStore];
        [self loadStore];
    } else {
        NSLog(@"SKIPPED setupCoreData, there's an existing Store:\n ** _store(%@)\n ** _iCloudStore(%@)", _store, _iCloudStore);
    }
}

#pragma mark - SAVING
- (void)saveContext {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if ([_context hasChanges]) {
        NSError *error = nil;
        if ([_context save:&error]) {
            NSLog(@"_context SAVED changes to persistent store");
        } else {
            NSLog(@"Failed to save _context: %@", error);
            [self showValidationError:error];
        }
    } else {
        NSLog(@"SKIPPED _context save, there are no changes!");
    }
}
- (void)backgroundSaveContext {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // First, save the child context in the foreground (fast, all in memory)
    [self saveContext];

    // Then, save the parent context.
    [_parentContext performBlock:^{
        if ([_parentContext hasChanges]) {
            NSError *error = nil;
            if ([_parentContext save:&error]) {
                NSLog(@"_parentContext SAVED changes to persistent store");
            }
            else {
                NSLog(@"_parentContext FAILED to save: %@", error);
                [self showValidationError:error];
            }
        }
        else {
            NSLog(@"_parentContext SKIPPED saving as there are no changes");
        }
    }];
}

#pragma mark - MIGRATION MANAGER
- (BOOL)isMigrationNecessaryForStore:(NSURL*)storeUrl {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (![[NSFileManager defaultManager] fileExistsAtPath:[self storeURL].path]) {
        if (debug==1) {NSLog(@"SKIPPED MIGRATION: Source database missing.");}
        return NO;
    }
    NSError *error = nil;
    NSDictionary *sourceMetadata =
    [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
                                                               URL:storeUrl error:&error];
    NSManagedObjectModel *destinationModel = _coordinator.managedObjectModel;
    if ([destinationModel isConfiguration:nil
              compatibleWithStoreMetadata:sourceMetadata]) {
        if (debug==1) {
            NSLog(@"SKIPPED MIGRATION: Source is already compatible");}
        return NO;
    }
    return YES;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {

    if ([keyPath isEqualToString:@"migrationProgress"]) {

        dispatch_async(dispatch_get_main_queue(), ^{

            float progress =
            [[change objectForKey:NSKeyValueChangeNewKey] floatValue];
            self.migrationVC.progressView.progress = progress;
            int percentage = progress * 100;
            NSString *string =
            [NSString stringWithFormat:@"Migration Progress: %i%%",
             percentage];
            NSLog(@"%@",string);
            self.migrationVC.label.text = string;
        });
    }
}
- (BOOL)replaceStore:(NSURL*)old withStore:(NSURL*)new {

    BOOL success = NO;
    NSError *Error = nil;
    if ([[NSFileManager defaultManager]
         removeItemAtURL:old error:&Error]) {

        Error = nil;
        if ([[NSFileManager defaultManager]
             moveItemAtURL:new toURL:old error:&Error]) {
            success = YES;
        }
        else {
            if (debug==1) {NSLog(@"FAILED to re-home new store %@", Error);}
        }
    }
    else {
        if (debug==1) {
            NSLog(@"FAILED to remove old store %@: Error:%@", old, Error);
        }
    }
    return success;
}
- (BOOL)migrateStore:(NSURL*)sourceStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    BOOL success = NO;
    NSError *error = nil;

    // STEP 1 - Gather the Source, Destination and Mapping Model
    NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator
                                    metadataForPersistentStoreOfType:NSSQLiteStoreType
                                    URL:sourceStore
                                    error:&error];

    NSManagedObjectModel *sourceModel =
    [NSManagedObjectModel mergedModelFromBundles:nil
                                forStoreMetadata:sourceMetadata];

    NSManagedObjectModel *destinModel = _model;

    NSMappingModel *mappingModel =
    [NSMappingModel mappingModelFromBundles:nil
                             forSourceModel:sourceModel
                           destinationModel:destinModel];

    // STEP 2 - Perform migration, assuming the mapping model isn't null
    if (mappingModel) {
        NSError *error = nil;
        NSMigrationManager *migrationManager =
        [[NSMigrationManager alloc] initWithSourceModel:sourceModel
                                       destinationModel:destinModel];
        [migrationManager addObserver:self
                           forKeyPath:@"migrationProgress"
                              options:NSKeyValueObservingOptionNew
                              context:NULL];

        NSURL *destinStore =
        [[self applicationStoresDirectory]
         URLByAppendingPathComponent:@"Temp.sqlite"];

        success =
        [migrationManager migrateStoreFromURL:sourceStore
                                         type:NSSQLiteStoreType options:nil
                             withMappingModel:mappingModel
                             toDestinationURL:destinStore
                              destinationType:NSSQLiteStoreType
                           destinationOptions:nil
                                        error:&error];
        if (success) {
            // STEP 3 - Replace the old store with the new migrated store
            if ([self replaceStore:sourceStore withStore:destinStore]) {
                if (debug==1) {
                    NSLog(@"SUCCESSFULLY MIGRATED %@ to the Current Model",
                          sourceStore.path);}
                [migrationManager removeObserver:self
                                      forKeyPath:@"migrationProgress"];
            }
        }
        else {
            if (debug==1) {NSLog(@"FAILED MIGRATION: %@",error);}
        }
    }
    else {
        if (debug==1) {NSLog(@"FAILED MIGRATION: Mapping Model is null");}
    }
    return YES; // indicates migration has finished, regardless of outcome
}
- (void)performBackgroundManagedMigrationForStore:(NSURL*)storeURL {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    // Show migration progress view preventing the user from using the app
    UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    self.migrationVC =
    [sb instantiateViewControllerWithIdentifier:@"migration"];
    UIApplication *sa = [UIApplication sharedApplication];
    UINavigationController *nc =
    (UINavigationController*)sa.keyWindow.rootViewController;
    [nc presentViewController:self.migrationVC animated:NO completion:nil];

    // Perform migration in the background, so it doesn't freeze the UI.
    // This way progress can be shown to the user
    dispatch_async(
                   dispatch_get_global_queue(
                                             DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
                       BOOL done = [self migrateStore:storeURL];
                       if(done) {
                           // When migration finishes, add the newly migrated store
                           dispatch_async(dispatch_get_main_queue(), ^{
                               NSError *error = nil;
                               _store =
                               [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                          configuration:nil
                                                                    URL:[self storeURL]
                                                                options:nil
                                                                  error:&error];
                               if (!_store) {
                                   NSLog(@"Failed to add a migrated store. Error: %@",
                                         error);abort();}
                               else {
                                   NSLog(@"Successfully added a migrated store: %@",
                                         _store);}
                               [self.migrationVC dismissViewControllerAnimated:NO
                                                                    completion:nil];
                               self.migrationVC = nil;
                           });
                       }
                   });
}

#pragma mark - VALIDATION ERROR HANDLING
- (void)showValidationError:(NSError *)anError {

    if (anError && [anError.domain isEqualToString:@"NSCocoaErrorDomain"]) {
        NSArray *errors = nil;  // holds all errors
        NSString *txt = @""; // the error message text of the alert

        // Populate array with error(s)
        if (anError.code == NSValidationMultipleErrorsError) {
            errors = [anError.userInfo objectForKey:NSDetailedErrorsKey];
        } else {
            errors = [NSArray arrayWithObject:anError];
        }
        // Display the error(s)
        if (errors && errors.count > 0) {
            // Build error message text based on errors
            for (NSError * error in errors) {
                NSString *entity =
                [[[error.userInfo objectForKey:@"NSValidationErrorObject"]entity]name];

                NSString *property =
                [error.userInfo objectForKey:@"NSValidationErrorKey"];

                switch (error.code) {
                    case NSValidationRelationshipDeniedDeleteError:
                        txt = [txt stringByAppendingFormat:
                               @"%@ delete was denied because there are associated %@\n(Error Code %li)\n\n", entity, property, (long)error.code];
                        break;
                    case NSValidationRelationshipLacksMinimumCountError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' relationship count is too small (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationRelationshipExceedsMaximumCountError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' relationship count is too large (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationMissingMandatoryPropertyError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' property is missing (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationNumberTooSmallError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' number is too small (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationNumberTooLargeError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' number is too large (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationDateTooSoonError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' date is too soon (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationDateTooLateError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' date is too late (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationInvalidDateError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' date is invalid (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationStringTooLongError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' text is too long (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationStringTooShortError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' text is too short (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationStringPatternMatchingError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' text doesn't match the specified pattern (Code %li).", property, (long)error.code];
                        break;
                    case NSManagedObjectValidationError:
                        txt = [txt stringByAppendingFormat:
                               @"generated validation error (Code %li)", (long)error.code];
                        break;

                    default:
                        txt = [txt stringByAppendingFormat:
                               @"Unhandled error code %li in showValidationError method", (long)error.code];
                        break;
                }
            }
            // display error message txt message
            UIAlertView *alertView =
            [[UIAlertView alloc] initWithTitle:@"Validation Error"

                                       message:[NSString stringWithFormat:@"%@Please double-tap the home button and close this application by swiping the application screenshot upwards",txt]
                                      delegate:nil
                             cancelButtonTitle:nil
                             otherButtonTitles:nil];
            [alertView show];
        }
    }
}

#pragma mark – DATA IMPORT
- (BOOL)isDefaultDataAlreadyImportedForStoreWithURL:(NSURL*)url
                                             ofType:(NSString*)type {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSError *error;
    NSDictionary *dictionary =
    [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type
                                                               URL:url
                                                             error:&error];
    if (error) {
        NSLog(@"Error reading persistent store metadata: %@",
              error.localizedDescription);
    }
    else {
        NSNumber *defaultDataAlreadyImported =
        [dictionary valueForKey:@"DefaultDataImported"];
        if (![defaultDataAlreadyImported boolValue]) {
            NSLog(@"Default Data has NOT already been imported");
            return NO;
        }
    }
    if (debug==1) {NSLog(@"Default Data HAS already been imported");}
    return YES;
}
- (void)checkIfDefaultDataNeedsImporting {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (![self isDefaultDataAlreadyImportedForStoreWithURL:[self storeURL]
                                                    ofType:NSSQLiteStoreType]) {
        self.importAlertView =
        [[UIAlertView alloc] initWithTitle:@"Import Default Data?"
                                   message:@"If you've never used Grocery Dude before then some default data might help you understand how to use it. Tap 'Import' to import default data. Tap 'Cancel' to skip the import, especially if you've done this before on other devices."
                                  delegate:self
                         cancelButtonTitle:@"Cancel"
                         otherButtonTitles:@"Import", nil];
        [self.importAlertView show];
    }
}
- (void)importFromXML:(NSURL*)url {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self.parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
    self.parser.delegate = self;

    NSLog(@"**** START PARSE OF %@", url.path);
    [self.parser parse];
    [[NSNotificationCenter defaultCenter]
     postNotificationName:@"SomethingChanged" object:nil];
    NSLog(@"***** END PARSE OF %@", url.path);
}
- (void)setDefaultDataAsImportedForStore:(NSPersistentStore*)aStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // get metadata dictionary
    NSMutableDictionary *dictionary =
    [NSMutableDictionary dictionaryWithDictionary:[[aStore metadata] copy]];

    if (debug==1) {
        NSLog(@"__Store Metadata BEFORE changes__ \n %@", dictionary);
    }

    // edit metadata dictionary
    [dictionary setObject:@YES forKey:@"DefaultDataImported"];

    // set metadata dictionary
    [self.coordinator setMetadata:dictionary forPersistentStore:aStore];

    if (debug==1) {NSLog(@"__Store Metadata AFTER changes__ \n %@", dictionary);}
}
- (void)setDefaultDataStoreAsInitialStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:self.storeURL.path]) {
        NSURL *defaultDataURL =
        [NSURL fileURLWithPath:[[NSBundle mainBundle]
                                pathForResource:@"DefaultData" ofType:@"sqlite"]];
        NSError *error;
        if (![fileManager copyItemAtURL:defaultDataURL
                                  toURL:self.storeURL
                                  error:&error]) {
            NSLog(@"DefaultData.sqlite copy FAIL: %@",
                  error.localizedDescription);
        }
        else {
            NSLog(@"A copy of DefaultData.sqlite was set as the initial store for %@",
                  self.storeURL.path);
        }
    }
}
- (void)deepCopyFromPersistentStore:(NSURL*)url {
    if (debug==1) {
        NSLog(@"Running %@ '%@' %@", self.class,
              NSStringFromSelector(_cmd),url.path);
    }
    // Periodically refresh the interface during the import
    _importTimer =
    [NSTimer scheduledTimerWithTimeInterval:2.0
                                     target:self
                                   selector:@selector(somethingChanged)
                                   userInfo:nil
                                    repeats:YES];

    [_sourceContext performBlock:^{

        NSLog(@"*** STARTED DEEP COPY FROM DEFAULT DATA PERSISTENT STORE ***");

        NSArray *entitiesToCopy = [NSArray arrayWithObjects:
                                   @"LocationAtHome",@"LocationAtShop",@"Unit",@"Item", nil];

        CoreDataImporter *importer = [[CoreDataImporter alloc]
                                      initWithUniqueAttributes:[self selectedUniqueAttributes]];

        [importer deepCopyEntities:entitiesToCopy
                       fromContext:_sourceContext
                         toContext:_importContext];

        [_context performBlock:^{
            // Stop periodically refreshing the interface
            [_importTimer invalidate];

            // Tell the interface to refresh once import completes
            [self somethingChanged];
        }];

        NSLog(@"*** FINISHED DEEP COPY FROM DEFAULT DATA PERSISTENT STORE ***");
    }];
}

#pragma mark – TEST DATA IMPORT (This code is Grocery Dude data specific)
- (void)importGroceryDudeTestData {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSNumber *imported =
    [[NSUserDefaults standardUserDefaults] objectForKey:@"TestDataImport"];

    if (!imported.boolValue) {
        NSLog(@"Importing test data...");
        [_importContext performBlock:^{

            NSManagedObject *locationAtHome =
            [NSEntityDescription insertNewObjectForEntityForName:@"LocationAtHome"
                                          inManagedObjectContext:_importContext];
            NSManagedObject *locationAtShop =
            [NSEntityDescription insertNewObjectForEntityForName:@"LocationAtShop"
                                          inManagedObjectContext:_importContext];
            [locationAtHome setValue:@"Test Home Location" forKey:@"storedIn"];
            [locationAtShop setValue:@"Test Shop Location" forKey:@"aisle"];

            for (int a = 1; a < 101; a++) {                                  @autoreleasepool {                                          // Insert Item                     NSManagedObject *item =                     [NSEntityDescription insertNewObjectForEntityForName:@"Item"                                                   inManagedObjectContext:_importContext];                     [item setValue:[NSString stringWithFormat:@"Test Item %i",a]                             forKey:@"name"];                     [item setValue:locationAtHome                             forKey:@"locationAtHome"];                     [item setValue:locationAtShop                             forKey:@"locationAtShop"];                                          // Insert Photo                     NSManagedObject *photo =                     [NSEntityDescription insertNewObjectForEntityForName:@"Item_Photo"                                                   inManagedObjectContext:_importContext];                     [photo setValue:UIImagePNGRepresentation(                                                              [UIImage imageNamed:@"GroceryHead.png"])                              forKey:@"data"];                                          // Relate Item and Photo                     [item setValue:photo forKey:@"photo"];                                          NSLog(@"Inserting %@", [item valueForKey:@"name"]);                     [Faulter faultObjectWithID:photo.objectID                                      inContext:_importContext];                     [Faulter faultObjectWithID:item.objectID                                      inContext:_importContext];                 }             }             [_importContext reset];                          // ensure import was a one off             [[NSUserDefaults standardUserDefaults]              setObject:[NSNumber numberWithBool:YES]              forKey:@"TestDataImport"];             [[NSUserDefaults standardUserDefaults] synchronize];         }];     }     else {         NSLog(@"Skipped test data import");     } } #pragma mark - DELEGATE: UIAlertView - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {     if (debug==1) {         NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));     }     if (alertView == self.importAlertView) {         if (buttonIndex == 1) { // The ‘Import’ button on the importAlertView                          NSLog(@"Default Data Import Approved by User");             // XML Import             [_importContext performBlock:^{                 [self importFromXML:[[NSBundle mainBundle]                                      URLForResource:@"DefaultData"                                      withExtension:@"xml"]];             }];             // Deep Copy Import From Persistent Store             //[self loadSourceStore];             //[self deepCopyFromPersistentStore:[self sourceStoreURL]];                      } else {             NSLog(@"Default Data Import Cancelled by User");         }         // Set the data as imported regardless of the user's decision         [self setDefaultDataAsImportedForStore:_store];     } } #pragma mark - UNIQUE ATTRIBUTE SELECTION (This code is Grocery Dude data specific and is used when instantiating CoreDataImporter) - (NSDictionary*)selectedUniqueAttributes {     if (debug==1) {         NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));     }     NSMutableArray *entities   = [NSMutableArray new];     NSMutableArray *attributes = [NSMutableArray new];          // Select an attribute in each entity for uniqueness     [entities addObject:@"Item"];[attributes addObject:@"name"];     [entities addObject:@"Unit"];[attributes addObject:@"name"];     [entities addObject:@"LocationAtHome"];[attributes addObject:@"storedIn"];     [entities addObject:@"LocationAtShop"];[attributes addObject:@"aisle"];     [entities addObject:@"Item_Photo"];[attributes addObject:@"data"];          NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:attributes                                                            forKeys:entities];     return dictionary; } #pragma mark - DELEGATE: NSXMLParser (This code is Grocery Dude data specific) - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {     if (debug==1) {         NSLog(@"Parser Error: %@", parseError.localizedDescription);     } } - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName   namespaceURI:(NSString *)namespaceURI  qualifiedName:(NSString *)qName     attributes:(NSDictionary *)attributeDict {          [self.importContext performBlockAndWait:^{                  // STEP 1: Process only the 'item' element in the XML file         if ([elementName isEqualToString:@"item"]) {                          // STEP 2: Prepare the Core Data Importer             CoreDataImporter *importer =             [[CoreDataImporter alloc] initWithUniqueAttributes:              [self selectedUniqueAttributes]];                          // STEP 3a: Insert a unique 'Item' object             NSManagedObject *item =             [importer insertBasicObjectInTargetEntity:@"Item"                                 targetEntityAttribute:@"name"                                    sourceXMLAttribute:@"name"                                         attributeDict:attributeDict                                               context:_importContext];                          // STEP 3b: Insert a unique 'Unit' object             NSManagedObject *unit =             [importer insertBasicObjectInTargetEntity:@"Unit"                                 targetEntityAttribute:@"name"                                    sourceXMLAttribute:@"unit"                                         attributeDict:attributeDict                                               context:_importContext];                          // STEP 3c: Insert a unique 'LocationAtHome' object             NSManagedObject *locationAtHome =             [importer insertBasicObjectInTargetEntity:@"LocationAtHome"                                 targetEntityAttribute:@"storedIn"                                    sourceXMLAttribute:@"locationathome"                                         attributeDict:attributeDict                                               context:_importContext];                          // STEP 3d: Insert a unique 'LocationAtShop' object             NSManagedObject *locationAtShop =             [importer insertBasicObjectInTargetEntity:@"LocationAtShop"                                 targetEntityAttribute:@"aisle"                                    sourceXMLAttribute:@"locationatshop"                                         attributeDict:attributeDict                                               context:_importContext];                          // STEP 4: Manually add extra attribute values.             [item setValue:@NO forKey:@"listed"];                          // STEP 5: Create relationships             [item setValue:unit forKey:@"unit"];             [item setValue:locationAtHome forKey:@"locationAtHome"];             [item setValue:locationAtShop forKey:@"locationAtShop"];                          // STEP 6: Save new objects to the persistent store.             [CoreDataImporter saveContext:_importContext];                          // STEP 7: Turn objects into faults to save memory             [Faulter faultObjectWithID:item.objectID inContext:_importContext];             [Faulter faultObjectWithID:unit.objectID inContext:_importContext];             [Faulter faultObjectWithID:locationAtHome.objectID inContext:_importContext];             [Faulter faultObjectWithID:locationAtShop.objectID inContext:_importContext];         }     }]; } #pragma mark – UNDERLYING DATA CHANGE NOTIFICATION - (void)somethingChanged {     if (debug==1) {         NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));     }     // Send a notification that tells observing interfaces to refresh their data     [[NSNotificationCenter defaultCenter]      postNotificationName:@"SomethingChanged" object:nil]; } #pragma mark - CORE DATA RESET - (void)resetContext:(NSManagedObjectContext*)moc {     [moc performBlockAndWait:^{         [moc reset];     }]; } - (BOOL)reloadStore {     BOOL success = NO;     NSError *error = nil;     if (![_coordinator removePersistentStore:_store error:&error]) {         NSLog(@"Unable to remove persistent store : %@", error);     }     [self resetContext:_sourceContext];     [self resetContext:_importContext];     [self resetContext:_context];     [self resetContext:_parentContext];     _store = nil;     [self setupCoreData];     [self somethingChanged];     if (_store) {success = YES;}     return success; } - (void)removeAllStoresFromCoordinator:(NSPersistentStoreCoordinator*)psc {     if (debug==1) {         NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));     }     for (NSPersistentStore *s in psc.persistentStores) {         NSError *error = nil;         if (![psc removePersistentStore:s error:&error]) {             NSLog(@"Error removing persistent store: %@", error);         }     } } - (void)resetCoreData {     if (debug==1) {         NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));     }     [_importContext performBlockAndWait:^{         [_importContext save:nil];         [self resetContext:_importContext];     }];     [_context performBlockAndWait:^{         [_context save:nil];         [self resetContext:_context];     }];     [_parentContext performBlockAndWait:^{         [_parentContext save:nil];         [self resetContext:_parentContext];     }];     [self removeAllStoresFromCoordinator:_coordinator];     _store = nil;     _iCloudStore = nil; } #pragma mark - ICLOUD - (BOOL)iCloudAccountIsSignedIn {     if (debug==1) {         NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));     }     id token = [[NSFileManager defaultManager] ubiquityIdentityToken];     if (token) {         NSLog(@"** iCloud is SIGNED IN with token '%@' **", token);         return YES;     }     NSLog(@"** iCloud is NOT SIGNED IN **");     NSLog(@"--> Is iCloud Documents and Data enabled for a valid iCloud account on your Mac & iOS Device or iOS Simulator?");
    NSLog(@"--> Have you enabled the iCloud Capability in the Application Target?");
    NSLog(@"--> Is there a CODE_SIGN_ENTITLEMENTS Xcode warning that needs fixing? You may need to specifically choose a developer instead of using Automatic selection");
    NSLog(@"--> Are you using a Pre-iOS7 Simulator?");
    return NO;
}
- (BOOL)loadiCloudStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (_iCloudStore) {return YES;} // Don’t load iCloud store if it’s already loaded

    NSDictionary *options =
    @{
      NSMigratePersistentStoresAutomaticallyOption:@YES
      ,NSInferMappingModelAutomaticallyOption:@YES
      ,NSPersistentStoreUbiquitousContentNameKey:@"Grocery-Dude"
      //,NSPersistentStoreUbiquitousContentURLKey:@"ChangeLogs" // Optional since iOS7
      };
    NSError *error;
    _iCloudStore = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                              configuration:nil
                                                        URL:[self iCloudStoreURL]
                                                    options:options
                                                      error:&error];
    if (_iCloudStore) {
        NSLog(@"** The iCloud Store has been successfully configured at '%@' **",
              _iCloudStore.URL.path);
        return YES;
    }
    NSLog(@"** FAILED to configure the iCloud Store : %@ **", error);
    return NO;
}
- (void)listenForStoreChanges {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSNotificationCenter *dc = [NSNotificationCenter defaultCenter];
    [dc addObserver:self
           selector:@selector(storesWillChange:)
               name:NSPersistentStoreCoordinatorStoresWillChangeNotification
             object:_coordinator];

    [dc addObserver:self
           selector:@selector(storesDidChange:)
               name:NSPersistentStoreCoordinatorStoresDidChangeNotification
             object:_coordinator];

    [dc addObserver:self
           selector:@selector(persistentStoreDidImportUbiquitiousContentChanges:)
               name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
             object:_coordinator];
}
- (void)storesWillChange:(NSNotification *)n {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [_importContext performBlockAndWait:^{
        [_importContext save:nil];
        [self resetContext:_importContext];
    }];
    [_context performBlockAndWait:^{
        [_context save:nil];
        [self resetContext:_context];
    }];
    [_parentContext performBlockAndWait:^{
        [_parentContext save:nil];
        [self resetContext:_parentContext];
    }];

    // Refresh UI
    [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                        object:nil
                                                      userInfo:nil];
}
- (void)storesDidChange:(NSNotification *)n {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Refresh UI
    [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                        object:nil
                                                      userInfo:nil];
}
- (void)persistentStoreDidImportUbiquitiousContentChanges:(NSNotification*)n {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [_context performBlock:^{
        [_context mergeChangesFromContextDidSaveNotification:n];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                            object:nil];
    }];
}
- (BOOL)iCloudEnabledByUser {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [[NSUserDefaults standardUserDefaults] synchronize]; // Ensure current value
    if ([[[NSUserDefaults standardUserDefaults]
          objectForKey:@"iCloudEnabled"] boolValue]) {
        NSLog(@"** iCloud is ENABLED in Settings **");
        return YES;
    }
    NSLog(@"** iCloud is DISABLED in Settings **");
    return NO;
}
- (void)ensureAppropriateStoreIsLoaded {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (!_store && !_iCloudStore) {
        return; // If neither store is loaded, skip (usually first launch)
    }
    if (![self iCloudEnabledByUser] && _store) {
        NSLog(@"The Non-iCloud Store is loaded as it should be");
        return;
    }
    if ([self iCloudEnabledByUser] && _iCloudStore) {
        NSLog(@"The iCloud Store is loaded as it should be");
        return;
    }
    NSLog(@"** The user preference on using iCloud with this application appears to have changed. Core Data will now be reset. **");

    [self resetCoreData];
    [self setupCoreData];

    [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                        object:nil];

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:
                          @"Your preference on using iCloud with this application appears to have changed"
                                                    message:
                          @"Content has been updated accordingly"
                                                   delegate:nil
                                          cancelButtonTitle:nil
                                          otherButtonTitles:@"Ok", nil];
    [alert show];
}

@end

 

CoreDataTVC.h

#import 
#import "CoreDataHelper.h"
@interface CoreDataTVC : UITableViewController

@property (strong, nonatomic) NSFetchedResultsController *frc;
@property (strong, nonatomic) NSFetchedResultsController *searchFRC;
@property (strong, nonatomic) UISearchDisplayController *searchDC;
- (void)performFetch;
- (NSFetchedResultsController*)frcFromTV:(UITableView*)tableView;
- (UITableView*)TVFromFRC:(NSFetchedResultsController*)frc;
- (void)reloadSearchFRCForPredicate:(NSPredicate*)predicate
                         withEntity:(NSString*)entity
                          inContext:(NSManagedObjectContext*)context
                withSortDescriptors:(NSArray*)sortDescriptors
             withSectionNameKeyPath:(NSString*)sectionNameKeyPath;
- (void)configureSearch;
@end

#import "CoreDataTVC.h"
@implementation CoreDataTVC
#define debug 0

#pragma mark - FETCHING
- (void)performFetch {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    
    if (self.frc) {
        [self.frc.managedObjectContext performBlockAndWait:^{
            
            NSError *error = nil;
            if (![self.frc performFetch:&error]) {
                
                NSLog(@"Failed to perform fetch: %@", error);
            }
            [self.tableView reloadData];
        }];
    } else {
        NSLog(@"Failed to fetch, the fetched results controller is nil.");
    }
}

#pragma mark - DATASOURCE: UITableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[[[self frcFromTV:tableView]sections]
             objectAtIndex:section] numberOfObjects];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[[self frcFromTV:tableView] sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[self frcFromTV:tableView]
            sectionForSectionIndexTitle:title atIndex:index];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[[[self frcFromTV:tableView] sections] objectAtIndex:section] name];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[self frcFromTV:tableView] sectionIndexTitles];
}

#pragma mark - DELEGATE: NSFetchedResultsController
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [[self TVFromFRC:controller] beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [[self TVFromFRC:controller] endUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id )sectionInfo
           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [[self TVFromFRC:controller]
             insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
             withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeDelete:
            [[self TVFromFRC:controller]
             deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
             withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}
- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    
    switch(type) {
            
        case NSFetchedResultsChangeInsert:
            [[self TVFromFRC:controller]
             insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
             withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
            
        case NSFetchedResultsChangeDelete:
            [[self TVFromFRC:controller]
             deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
             withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
            
        case NSFetchedResultsChangeUpdate:
            if (!newIndexPath) {
                [[self TVFromFRC:controller]
                 reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                 withRowAnimation:UITableViewRowAnimationNone];
            }
            else {
                [[self TVFromFRC:controller]
                 deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                 withRowAnimation:UITableViewRowAnimationNone];
                [[self TVFromFRC:controller]
                 insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                 withRowAnimation:UITableViewRowAnimationNone];
            }
            break;
            
        case NSFetchedResultsChangeMove:
            [[self TVFromFRC:controller]
             deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
             withRowAnimation:UITableViewRowAnimationAutomatic];
            [[self TVFromFRC:controller]
             insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
             withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
    }
}

#pragma mark - GENERAL
- (NSFetchedResultsController*)frcFromTV:(UITableView*)tableView {
    /*
     If the given tableView is self.tableView return self.frc,
     otherwise self.searchFRC
     */
    return (tableView == self.tableView) ? self.frc : self.searchFRC;
}
- (UITableView*)TVFromFRC:(NSFetchedResultsController*)frc {
    /*
     If the given fetched results controller is self.frc return self.tableView,
     otherwise self.searchDC.searchResultsTableView
     */
    return (frc == self.frc) ? self.tableView : self.searchDC.searchResultsTableView;
}

#pragma mark - DELEGATE: UISearchDisplayController
- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self.searchFRC.delegate = nil;
    self.searchFRC = nil;
}

#pragma mark - SEARCH
- (void)reloadSearchFRCForPredicate:(NSPredicate*)predicate
                         withEntity:(NSString*)entity
                          inContext:(NSManagedObjectContext*)context
                withSortDescriptors:(NSArray*)sortDescriptors
             withSectionNameKeyPath:(NSString*)sectionNameKeyPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:entity];
    request.sortDescriptors = sortDescriptors;
    request.predicate = predicate;
    request.fetchBatchSize = 15;
    
    self.searchFRC =
    [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                        managedObjectContext:context
                                          sectionNameKeyPath:sectionNameKeyPath
                                                   cacheName:nil];
    self.searchFRC.delegate = self;
    
    [self.searchFRC.managedObjectContext performBlockAndWait:^{
        NSError *error;
        if (![self.searchFRC performFetch:&error]) {
            NSLog(@"SEARCH FETCH ERROR: %@", error);
        }
    }];
}
- (void)configureSearch {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    UISearchBar *searchBar =
    [[UISearchBar alloc] initWithFrame:
     CGRectMake(0, 0, self.tableView.frame.size.width, 44.0)];
    searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
    self.tableView.tableHeaderView = searchBar;
    
    self.searchDC =
    [[UISearchDisplayController alloc] initWithSearchBar:searchBar
                                      contentsController:self];
    self.searchDC.delegate = self;
    self.searchDC.searchResultsDataSource = self;
    self.searchDC.searchResultsDelegate = self;
}

@end

说明:

1.获取objectID unitVC.selectedObjectID =[[self.frc objectAtIndexPath:indexPath] objectID];

2.传递的是objectID,通过[cdh.context existingObjectWithID:self.selectedObjectIDerror:nil] 获取该object;

itemtvc

vc

PrepareTVC

#import <UIKit/UIKit.h>
#import "CoreDataTVC.h"
@interface PrepareTVC : CoreDataTVC 
@property (strong, nonatomic) UIActionSheet *clearConfirmActionSheet;
@end

#import "PrepareTVC.h"
#import "CoreDataHelper.h"
#import "Item.h"
#import "Unit.h"
#import "AppDelegate.h"
#import "ItemVC.h"
#import "Thumbnailer.h"

@implementation PrepareTVC
#define debug 0

#pragma mark - DATA
- (void)configureFetch {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];

    NSFetchRequest *request =
    [NSFetchRequest fetchRequestWithEntityName:@"Item"];

    request.sortDescriptors =
    [NSArray arrayWithObjects:
     [NSSortDescriptor sortDescriptorWithKey:@"locationAtHome.storedIn"
                                   ascending:YES],
     [NSSortDescriptor sortDescriptorWithKey:@"name"
                                   ascending:YES],
     nil];
    [request setFetchBatchSize:15];
    self.frc =
    [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                        managedObjectContext:cdh.context
                                          sectionNameKeyPath:@"locationAtHome.storedIn"
                                                   cacheName:nil];
    self.frc.delegate = self;
}

#pragma mark - VIEW
- (void)viewDidAppear:(BOOL)animated {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidAppear:animated];

    // Create missing Thumbnails
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSArray *sortDescriptors =
    [NSArray arrayWithObjects:
     [NSSortDescriptor sortDescriptorWithKey:@"locationAtHome.storedIn"
                                   ascending:YES],
     [NSSortDescriptor sortDescriptorWithKey:@"name"
                                   ascending:YES],
     nil];

    [Thumbnailer createMissingThumbnailsForEntityName:@"Item"
                           withThumbnailAttributeName:@"thumbnail"
                            withPhotoRelationshipName:@"photo"
                               withPhotoAttributeName:@"data"
                                  withSortDescriptors:sortDescriptors
                                    withImportContext:cdh.importContext];
}
- (void)viewDidLoad {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidLoad];
    [self configureFetch];
    [self performFetch];
    self.clearConfirmActionSheet.delegate = self;

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(performFetch)
                                                 name:@"SomethingChanged"
                                               object:nil];
    [self configureSearch];
}
- (UITableViewCell*)tableView:(UITableView *)tableView
        cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    static NSString *cellIdentifier = @"Item Cell";

    UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
                                      reuseIdentifier:cellIdentifier];
    }
    cell.accessoryType = UITableViewCellAccessoryDetailButton;
    Item *item = [[self frcFromTV:tableView] objectAtIndexPath:indexPath];

    NSMutableString *title = [NSMutableString stringWithFormat:@"%@%@ %@",
                              item.quantity, item.unit.name, item.name];
    [title replaceOccurrencesOfString:@"(null)"
                           withString:@""
                              options:0
                                range:NSMakeRange(0, [title length])];
    cell.textLabel.text = title;

    // make selected items orange
    if ([item.listed boolValue]) {
        [cell.textLabel setFont:[UIFont fontWithName:@"Helvetica Neue" size:18]];
        [cell.textLabel setTextColor:[UIColor orangeColor]];
    }
    else {
        [cell.textLabel setFont:[UIFont fontWithName:@"Helvetica Neue" size:16]];
        [cell.textLabel setTextColor:[UIColor grayColor]];
    }
    cell.imageView.image = [UIImage imageWithData:item.thumbnail];
    return cell;
}
- (NSArray*)sectionIndexTitlesForTableView:(UITableView *)tableView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return nil; // we don't want a section index.
}
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (editingStyle == UITableViewCellEditingStyleDelete) {

        NSFetchedResultsController *frc = [self frcFromTV:tableView];
        Item *deleteTarget = [frc objectAtIndexPath:indexPath];
        [frc.managedObjectContext deleteObject:deleteTarget];
        [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                         withRowAnimation:UITableViewRowAnimationFade];
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];
}
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath  {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSFetchedResultsController *frc = [self frcFromTV:tableView];
    NSManagedObjectID *itemid = [[frc objectAtIndexPath:indexPath] objectID];

    Item *item =
    (Item*)[frc.managedObjectContext existingObjectWithID:itemid error:nil];
    if ([item.listed boolValue]) {
        item.listed = [NSNumber numberWithBool:NO];
    }
    else {
        item.listed = [NSNumber numberWithBool:YES];
        item.collected = [NSNumber numberWithBool:NO];
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];
}

#pragma mark - INTERACTION
- (IBAction)clear:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSFetchRequest *request =
    [cdh.model fetchRequestTemplateForName:@"ShoppingList"];
    NSArray *shoppingList =
    [cdh.context executeFetchRequest:request error:nil];

    if (shoppingList.count > 0) {

        self.clearConfirmActionSheet =
        [[UIActionSheet alloc] initWithTitle:@"Clear Entire Shopping List?"
                                    delegate:self
                           cancelButtonTitle:@"Cancel"
                      destructiveButtonTitle:@"Clear"
                           otherButtonTitles:nil];
        [self.clearConfirmActionSheet
         showFromTabBar:self.navigationController.tabBarController.tabBar];
    }
    else {
        UIAlertView *alert =
        [[UIAlertView alloc] initWithTitle:@"Nothing to Clear"
                                   message:@"Add items to the Shop tab by tapping them on the Prepare tab. Remove all items from the Shop tab by clicking Clear on the Prepare tab"
                                  delegate:nil
                         cancelButtonTitle:@"Ok"
                         otherButtonTitles:nil];
        [alert show];
    }
    shoppingList = nil;
    [cdh backgroundSaveContext];
}
- (void)actionSheet:(UIActionSheet *)actionSheet
clickedButtonAtIndex:(NSInteger)buttonIndex {

    if (actionSheet == self.clearConfirmActionSheet) {
        if (buttonIndex == [actionSheet destructiveButtonIndex]) {
            [self performSelector:@selector(clearList)];
        }
        else if (buttonIndex == [actionSheet cancelButtonIndex]){
            [actionSheet dismissWithClickedButtonIndex:
             [actionSheet cancelButtonIndex] animated:YES];
        }
    }
}
- (void)clearList {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSFetchRequest *request =
    [cdh.model fetchRequestTemplateForName:@"ShoppingList"];
    NSArray *shoppingList =
    [cdh.context executeFetchRequest:request error:nil];

    for (Item *item in shoppingList) {
        item.listed = [NSNumber numberWithBool:NO];
    }
    [cdh backgroundSaveContext];
}

#pragma mark - SEGUE
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    ItemVC *itemVC = segue.destinationViewController;
    if ([segue.identifier isEqualToString:@"Add Item Segue"])
    {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Item *newItem =
        [NSEntityDescription insertNewObjectForEntityForName:@"Item"
                                      inManagedObjectContext:cdh.context];
        NSError *error = nil;
        if (![cdh.context
              obtainPermanentIDsForObjects:[NSArray arrayWithObject:newItem]
              error:&error]) {
            NSLog(@"Couldn't obtain a permanent ID for object %@", error);
        }
        itemVC.selectedItemID = newItem.objectID;
    }
    else {
        NSLog(@"Unidentified Segue Attempted!");
    }
}
- (void)tableView:(UITableView *)tableView
accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    ItemVC *itemVC =
    [self.storyboard instantiateViewControllerWithIdentifier:@"ItemVC"];
    itemVC.selectedItemID =
    [[[self frcFromTV:tableView] objectAtIndexPath:indexPath] objectID];
    [self.navigationController pushViewController:itemVC animated:YES];
}

#pragma mark - SEARCH
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (searchString.length > 0) {
        NSLog(@"--> Searching for '%@'", searchString);
        NSPredicate *predicate =
        [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", searchString];

        NSArray *sortDescriptors =
        [NSArray arrayWithObjects:
         [NSSortDescriptor sortDescriptorWithKey:@"locationAtHome.storedIn"
                                       ascending:YES],
         [NSSortDescriptor sortDescriptorWithKey:@"name"
                                       ascending:YES], nil];

        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];

        [self reloadSearchFRCForPredicate:predicate
                               withEntity:@"Item"
                                inContext:cdh.context
                      withSortDescriptors:sortDescriptors
                   withSectionNameKeyPath:@"locationAtHome.storedIn"];
    } else {
        return NO;
    }
    return YES;
}
@end

ShopTVC.m

#import "ShopTVC.h"
#import "CoreDataHelper.h"
#import "Item.h"
#import "Unit.h"
#import "AppDelegate.h"
#import "ItemVC.h"
#import "Thumbnailer.h"

@implementation ShopTVC
#define debug 0

#pragma mark - DATA
- (void)configureFetch {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSFetchRequest *request =
    [[cdh.model fetchRequestTemplateForName:@"ShoppingList"] copy];

    request.sortDescriptors =
    [NSArray arrayWithObjects:
     [NSSortDescriptor sortDescriptorWithKey:@"locationAtShop.aisle"
                                   ascending:YES],
     [NSSortDescriptor sortDescriptorWithKey:@"name"
                                   ascending:YES],
     nil];
    [request setFetchBatchSize:15];

    self.frc =
    [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                        managedObjectContext:cdh.context
                                          sectionNameKeyPath:@"locationAtShop.aisle"
                                                   cacheName:nil];
    self.frc.delegate = self;
}

#pragma mark - VIEW
- (void)viewDidAppear:(BOOL)animated {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidAppear:animated];

    // Create missing Thumbnails
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSArray *sortDescriptors =
    [NSArray arrayWithObjects:
     [NSSortDescriptor sortDescriptorWithKey:@"locationAtHome.storedIn"
                                   ascending:YES],
     [NSSortDescriptor sortDescriptorWithKey:@"name"
                                   ascending:YES],
     nil];

    [Thumbnailer createMissingThumbnailsForEntityName:@"Item"
                           withThumbnailAttributeName:@"thumbnail"
                            withPhotoRelationshipName:@"photo"
                               withPhotoAttributeName:@"data"
                                  withSortDescriptors:sortDescriptors
                                    withImportContext:cdh.importContext];
}
- (void)viewDidLoad {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidLoad];
    [self configureFetch];
    [self performFetch];

    // Respond to changes in underlying store
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(performFetch)
                                                 name:@"SomethingChanged"
                                               object:nil];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    static NSString *cellIdentifier = @"Shop Cell";
    UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:cellIdentifier
                                    forIndexPath:indexPath];

    Item *item = [self.frc objectAtIndexPath:indexPath];
    NSMutableString *title = [NSMutableString stringWithFormat:@"%@%@ %@",
                              item.quantity, item.unit.name, item.name];
    [title replaceOccurrencesOfString:@"(null)"
                           withString:@""
                              options:0
                                range:NSMakeRange(0, [title length])];
    cell.textLabel.text = title;

    // make collected items green
    if (item.collected.boolValue) {
        [cell.textLabel setFont:[UIFont
                                 fontWithName:@"Helvetica Neue" size:16]];
        [cell.textLabel setTextColor:
         [UIColor colorWithRed:0.368627450
                         green:0.741176470
                          blue:0.349019607 alpha:1.0]];
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    }
    else {
        [cell.textLabel setFont:[UIFont
                                 fontWithName:@"Helvetica Neue" size:18]];
        cell.textLabel.textColor = [UIColor orangeColor];
        cell.accessoryType = UITableViewCellAccessoryDetailButton;
    }

    cell.imageView.image = [UIImage imageWithData:item.thumbnail];

    return cell;
}
- (NSArray*)sectionIndexTitlesForTableView:(UITableView *)tableView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return nil; // prevent section index.
}
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath  {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    Item *item = [self.frc objectAtIndexPath:indexPath];
    if (item.collected.boolValue) {
        item.collected = [NSNumber numberWithBool:NO];
    }
    else {
        item.collected = [NSNumber numberWithBool:YES];
    }
    [self.tableView reloadRowsAtIndexPaths:
     [NSArray  arrayWithObject:indexPath]
                          withRowAnimation:UITableViewRowAnimationNone];

    CoreDataHelper *cdh = [(AppDelegate *)[[UIApplication
                                            sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];
}

#pragma mark - INTERACTION
- (IBAction)clear:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if ([self.frc.fetchedObjects count] == 0) {

        UIAlertView *alert =
        [[UIAlertView alloc] initWithTitle:@"Nothing to Clear"
                                   message:@"Add items using the Prepare tab"
                                  delegate:nil
                         cancelButtonTitle:@"Ok" otherButtonTitles:nil];
        [alert show];
        return;
    }
    BOOL nothingCleared = YES;
    for (Item *item in self.frc.fetchedObjects) {

        if (item.collected.boolValue)
        {
            item.listed = [NSNumber numberWithBool:NO];
            item.collected = [NSNumber numberWithBool:NO];
            nothingCleared = NO;
        }
    }
    if (nothingCleared) {
        UIAlertView *alert =
        [[UIAlertView alloc] initWithTitle:nil message:
         @"Select items to be removed from the list before pressing Clear"
                                  delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil];
        [alert show];
    }
    CoreDataHelper *cdh = [(AppDelegate *)[[UIApplication
                                            sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];
}

#pragma mark - SEGUE
- (void)tableView:(UITableView *)tableView
accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    ItemVC *itemVC =
    [self.storyboard instantiateViewControllerWithIdentifier:@"ItemVC"];
    itemVC.selectedItemID =
    [[self.frc objectAtIndexPath:indexPath] objectID];
    [self.navigationController pushViewController:itemVC animated:YES];
}
@end

ItemVC

itemVC

#import <UIKit/UIKit.h>
#import "CoreDataHelper.h"
#import "UnitPickerTF.h"
#import "LocationAtHomePickerTF.h" 
#import "LocationAtShopPickerTF.h"

@interface ItemVC : UIViewController <UITextFieldDelegate,CoreDataPickerTFDelegate,UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (strong, nonatomic) NSManagedObjectID *selectedItemID;
@property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
@property (strong, nonatomic) IBOutlet UITextField *nameTextField;
@property (strong, nonatomic) IBOutlet UITextField *quantityTextField;
@property (strong, nonatomic) IBOutlet UnitPickerTF *unitPickerTextField;
@property (strong, nonatomic) IBOutlet LocationAtHomePickerTF *homeLocationPickerTextField;
@property (strong, nonatomic) IBOutlet LocationAtShopPickerTF *shopLocationPickerTextField;
@property (strong, nonatomic) IBOutlet UITextField *activeField;
@property (strong, nonatomic) IBOutlet UIImageView *photoImageView;
@property (strong, nonatomic) IBOutlet UIButton *cameraButton;
@property (strong, nonatomic) UIImagePickerController *camera;
@end

ItemVC

#import "ItemVC.h"
#import "AppDelegate.h" 
#import "Item.h"
#import "LocationAtHome.h"
#import "LocationAtShop.h"
#import "Unit.h"
#import "Item_Photo.h"

@implementation ItemVC
#define debug 0

#pragma mark - INTERACTION
- (IBAction)done:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self hideKeyboard];
    [self.navigationController popViewControllerAnimated:YES];
}
- (void)hideKeyboardWhenBackgroundIsTapped {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    UITapGestureRecognizer *tgr =
    [[UITapGestureRecognizer alloc] initWithTarget:self
                                            action:@selector(hideKeyboard)];
    [tgr setCancelsTouchesInView:NO];
    [self.view addGestureRecognizer:tgr];
}
- (void)hideKeyboard {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self.view endEditing:YES];
}
- (void)keyboardDidShow:(NSNotification *)n {

    // Find top of keyboard input view (i.e. picker)
    CGRect keyboardRect =
    [[[n userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    keyboardRect = [self.view convertRect:keyboardRect fromView:nil];
    CGFloat keyboardTop = keyboardRect.origin.y;

    // Resize scroll view
    CGRect newScrollViewFrame =
    CGRectMake(0, 0, self.view.bounds.size.width, keyboardTop);
    newScrollViewFrame.size.height = keyboardTop - self.view.bounds.origin.y;
    [self.scrollView setFrame:newScrollViewFrame];

    // Scroll to the active Text-Field
    [self.scrollView scrollRectToVisible:self.activeField.frame animated:YES];
}
- (void)keyboardWillHide:(NSNotification *)n {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CGRect defaultFrame =
    CGRectMake(self.scrollView.frame.origin.x,
               self.scrollView.frame.origin.y,
               self.view.frame.size.width,
               self.view.frame.size.height);

    // Reset Scrollview to the same size as the containing view
    [self.scrollView setFrame:defaultFrame];

    // Scroll to the top again
    [self.scrollView scrollRectToVisible:self.nameTextField.frame
                                animated:YES];
}

#pragma mark - DELEGATE: UITextField
- (void)textFieldDidBeginEditing:(UITextField *)textField {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (textField == self.nameTextField) {

        if ([self.nameTextField.text isEqualToString:@"New Item"]) {
            self.nameTextField.text = @"";
        }
    }
    if (textField == _unitPickerTextField && _unitPickerTextField.picker) {
        [_unitPickerTextField fetch];
        [_unitPickerTextField.picker reloadAllComponents];
    } else if (textField == _homeLocationPickerTextField &&
               _homeLocationPickerTextField.picker) {
        [_homeLocationPickerTextField fetch];
        [_homeLocationPickerTextField.picker reloadAllComponents];
    } else if (textField == _shopLocationPickerTextField &&
               _shopLocationPickerTextField.picker) {
        [_shopLocationPickerTextField fetch];
        [_shopLocationPickerTextField.picker reloadAllComponents];
    }
    _activeField = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    Item *item =
    (Item*)[cdh.context existingObjectWithID:self.selectedItemID error:nil];

    if (textField == self.nameTextField) {
        if ([self.nameTextField.text isEqualToString:@""]) {
            self.nameTextField.text = @"New Item";
        }
        item.name = self.nameTextField.text;
    }
    else if (textField == self.quantityTextField) {
        item.quantity =
        [NSNumber numberWithFloat:self.quantityTextField.text.floatValue];
    }
}

#pragma mark - VIEW
- (void)refreshInterface {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedItemID) {
        CoreDataHelper *cdh = [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Item *item = (Item*)[cdh.context existingObjectWithID:self.selectedItemID error:nil];
        self.nameTextField.text = item.name;
        self.quantityTextField.text = item.quantity.stringValue;
        self.unitPickerTextField.text = item.unit.name;
        self.unitPickerTextField.selectedObjectID = item.unit.objectID;
        self.homeLocationPickerTextField.text = item.locationAtHome.storedIn;
        self.homeLocationPickerTextField.selectedObjectID = item.locationAtHome.objectID;
        self.shopLocationPickerTextField.text = item.locationAtShop.aisle;
        self.shopLocationPickerTextField.selectedObjectID = item.locationAtShop.objectID;

        [cdh.context performBlock:^{
            self.photoImageView.image =
            [UIImage imageWithData:item.photo.data];
        }];

        [self checkCamera];
    }
}
- (void)viewDidLoad {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidLoad];
    [self hideKeyboardWhenBackgroundIsTapped];
    self.nameTextField.delegate = self;
    self.quantityTextField.delegate = self;

    self.unitPickerTextField.delegate = self;
    self.unitPickerTextField.pickerDelegate = self;
    self.homeLocationPickerTextField.delegate = self;
    self.homeLocationPickerTextField.pickerDelegate = self;
    self.shopLocationPickerTextField.delegate = self;
    self.shopLocationPickerTextField.pickerDelegate = self;
}
- (void)viewWillAppear:(BOOL)animated {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    // Register for keyboard notifications while the view is visible.
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardDidShow:)
                                                 name:UIKeyboardDidShowNotification
                                               object:self.view.window];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillHide:)
                                                 name:UIKeyboardWillHideNotification
                                               object:self.view.window];

    [self ensureItemHomeLocationIsNotNull];
    [self ensureItemShopLocationIsNotNull];

    [self refreshInterface];
    if ([self.nameTextField.text isEqual:@"New Item"]) {
        self.nameTextField.text = @"";
        [self.nameTextField becomeFirstResponder];
    }
}
- (void)viewDidDisappear:(BOOL)animated {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self ensureItemHomeLocationIsNotNull];
    [self ensureItemShopLocationIsNotNull];

    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];

    // Unregister for keyboard notifications while the view is not visible.
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardDidShowNotification
                                                  object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillHideNotification
                                                  object:nil];

    // Turn item & item photo into a fault
    NSError *error;
    Item *item =
    (Item*)[cdh.context existingObjectWithID:self.selectedItemID error:&error];
    if (error) {
        NSLog(@"ERROR!!! --> %@", error.localizedDescription);
    } else {
        [cdh.context refreshObject:item.photo mergeChanges:NO];
        [cdh.context refreshObject:item mergeChanges:NO];
    }
}

#pragma mark - DATA
- (void)ensureItemHomeLocationIsNotNull {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedItemID) {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Item *item =
        (Item*)[cdh.context existingObjectWithID:self.selectedItemID
                                           error:nil];
        if (!item.locationAtHome) {
            NSFetchRequest *request =
            [[cdh model]
             fetchRequestTemplateForName:@"UnknownLocationAtHome"];
            NSArray *fetchedObjects =
            [cdh.context executeFetchRequest:request error:nil];

            if ([fetchedObjects count] > 0) {
                item.locationAtHome = [fetchedObjects objectAtIndex:0];
            }
            else {
                LocationAtHome *locationAtHome =
                [NSEntityDescription
                 insertNewObjectForEntityForName:@"LocationAtHome"
                 inManagedObjectContext:cdh.context];
                NSError *error = nil;
                if (![cdh.context obtainPermanentIDsForObjects:
                      [NSArray arrayWithObject:locationAtHome] error:&error]) {
                    NSLog(@"Couldn't obtain a permanent ID for object %@",
                          error);
                }
                locationAtHome.storedIn = @"..Unknown Location..";
                item.locationAtHome = locationAtHome;
            }
        }
    }
}
- (void)ensureItemShopLocationIsNotNull {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedItemID) {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Item *item =
        (Item*)[cdh.context existingObjectWithID:self.selectedItemID
                                           error:nil];
        if (!item.locationAtShop) {
            NSFetchRequest *request =
            [[cdh model]
             fetchRequestTemplateForName:@"UnknownLocationAtShop"];
            NSArray *fetchedObjects =
            [cdh.context executeFetchRequest:request error:nil];

            if ([fetchedObjects count] > 0) {
                item.locationAtShop = [fetchedObjects objectAtIndex:0];
            }
            else {
                LocationAtShop *locationAtShop =
                [NSEntityDescription
                 insertNewObjectForEntityForName:@"LocationAtShop"
                 inManagedObjectContext:cdh.context];
                NSError *error = nil;
                if (![cdh.context obtainPermanentIDsForObjects:
                      [NSArray arrayWithObject:locationAtShop] error:&error]) {
                    NSLog(@"Couldn't obtain a permanent ID for object %@",
                          error);
                }
                locationAtShop.aisle = @"..Unknown Location..";
                item.locationAtShop = locationAtShop;
            }
        }
    }
}

#pragma mark - PICKERS
- (void)selectedObjectID:(NSManagedObjectID *)objectID
      changedForPickerTF:(CoreDataPickerTF *)pickerTF {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedItemID) {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];

        Item *item =
        (Item*)[cdh.context existingObjectWithID:self.selectedItemID
                                           error:nil];;
        NSError *error;
        if (pickerTF == self.unitPickerTextField) {
            Unit *unit = (Unit*)[cdh.context existingObjectWithID:objectID
                                                            error:&error];
            item.unit = unit;
            self.unitPickerTextField.text = item.unit.name;
        }
        else if (pickerTF == self.homeLocationPickerTextField) {
            LocationAtHome *locationAtHome =
            (LocationAtHome*)[cdh.context existingObjectWithID:objectID
                                                         error:&error];
            item.locationAtHome = locationAtHome;
            self.homeLocationPickerTextField.text =
            item.locationAtHome.storedIn;
        }
        else if (pickerTF == self.shopLocationPickerTextField) {
            LocationAtShop *locationAtShop =
            (LocationAtShop*)[cdh.context existingObjectWithID:objectID
                                                         error:&error];
            item.locationAtShop = locationAtShop;
            self.shopLocationPickerTextField.text =
            item.locationAtShop.aisle;
        }
        [self refreshInterface];
        if (error) {
            NSLog(@"Error selecting object on picker: %@, %@",
                  error, error.localizedDescription);
        }
    }
}
- (void)selectedObjectClearedForPickerTF:(CoreDataPickerTF *)pickerTF {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedItemID) {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Item *item =
        (Item*)[cdh.context existingObjectWithID:self.selectedItemID
                                           error:nil];

        if (pickerTF == self.unitPickerTextField) {
            item.unit = nil;
            self.unitPickerTextField.text = @"";
        }
        else if (pickerTF == self.homeLocationPickerTextField) {
            item.LocationAtHome = nil;
            self.homeLocationPickerTextField.text = @"";
        }
        else if (pickerTF == self.shopLocationPickerTextField) {
            item.LocationAtShop = nil;
            self.shopLocationPickerTextField.text = @"";
        }
        [self refreshInterface];
    }
}

#pragma mark - CAMERA
- (void)checkCamera {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self.cameraButton.enabled =
    [UIImagePickerController
     isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
}
- (IBAction)showCamera:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if ([UIImagePickerController
         isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
    {
        NSLog(@"Camera is available");
        _camera = [[UIImagePickerController alloc] init];
        _camera.sourceType = UIImagePickerControllerSourceTypeCamera;
        _camera.mediaTypes =
        [UIImagePickerController
         availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
        _camera.allowsEditing = YES;
        _camera.delegate = self;
        [self.navigationController presentViewController:_camera
                                                animated:YES
                                              completion:nil];
    }
    else
    {
        NSLog(@"Camera not available");
    }
}
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];

    Item *item =
    (Item*)[cdh.context existingObjectWithID:self.selectedItemID error:nil];

    UIImage *photo =
    (UIImage *)[info objectForKey:UIImagePickerControllerEditedImage];

    NSLog(@"Captured %f x %f photo",photo.size.height, photo.size.width);

    if (!item.photo) { // Create photo object it doesn't exist
        Item_Photo *newPhoto =
        [NSEntityDescription insertNewObjectForEntityForName:@"Item_Photo"
                                      inManagedObjectContext:cdh.context];
        [cdh.context obtainPermanentIDsForObjects:
         [NSArray arrayWithObject:newPhoto] error:nil];
        item.photo = newPhoto;
    }
    item.photo.data = UIImageJPEGRepresentation(photo, 0.5);
    item.thumbnail = nil;

    self.photoImageView.image = photo;

    [picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [picker dismissViewControllerAnimated:YES completion:nil];
}

@end

vc

 

View – UnitesTVC

 

#import "UnitsTVC.h"
#import "CoreDataHelper.h"
#import "AppDelegate.h"
#import "Unit.h"
#import "UnitVC.h"

@implementation UnitsTVC
#define debug 0

#pragma mark - DATA
- (void)configureFetch {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSFetchRequest *request =
    [NSFetchRequest fetchRequestWithEntityName:@"Unit"];
    request.sortDescriptors = [NSArray arrayWithObjects:
                               [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES],nil];
    [request setFetchBatchSize:15];
    self.frc =
    [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                        managedObjectContext:cdh.context
                                          sectionNameKeyPath:nil
                                                   cacheName:nil];
    self.frc.delegate = self;
}

#pragma mark - VIEW
- (void)viewDidLoad {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidLoad];
    [self configureFetch];
    [self performFetch];
    // Respond to changes in underlying store
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(performFetch)
                                                 name:@"SomethingChanged"
                                               object:nil];
}
- (UITableViewCell*)tableView:(UITableView *)tableView
        cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    static NSString *cellIdentifier = @"Unit Cell";
    UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:cellIdentifier
                                    forIndexPath:indexPath];
    Unit *unit = [self.frc objectAtIndexPath:indexPath];
    cell.textLabel.text = unit.name;
    return cell;
}
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        Unit *deleteTarget = [self.frc objectAtIndexPath:indexPath];
        [self.frc.managedObjectContext deleteObject:deleteTarget];
        [self.tableView reloadRowsAtIndexPaths:
         [NSArray arrayWithObject:indexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
    }

    CoreDataHelper *cdh = [(AppDelegate *)[[UIApplication
                                            sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];
}

#pragma mark - INTERACTION
- (IBAction)done:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self.parentViewController
     dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - SEGUE
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    UnitVC *unitVC = segue.destinationViewController;
    if ([segue.identifier isEqualToString:@"Add Object Segue"])
    {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Unit *newUnit =
        [NSEntityDescription insertNewObjectForEntityForName:@"Unit"
                                      inManagedObjectContext:cdh.context];
        NSError *error = nil;
        if (![cdh.context obtainPermanentIDsForObjects:
              [NSArray arrayWithObject:newUnit] error:&error]) {
            NSLog(@"Couldn't obtain a permanent ID for object %@", error);
        }
        unitVC.selectedObjectID = newUnit.objectID;
    }
    else if ([segue.identifier isEqualToString:@"Edit Object Segue"])
    {
        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        unitVC.selectedObjectID =
        [[self.frc objectAtIndexPath:indexPath] objectID];
    }
    else {
        NSLog(@"Unidentified Segue Attempted!");
    }
}

@end
#import <UIKit/UIKit.h>
#import "CoreDataHelper.h"
@interface UnitVC : UIViewController 
@property (strong, nonatomic) NSManagedObjectID *selectedObjectID;
@property (strong, nonatomic) IBOutlet UITextField *nameTextField;
@end

#import "UnitVC.h"
#import "Unit.h"
#import "AppDelegate.h"

@implementation UnitVC
#define debug 0

#pragma mark - VIEW
- (void)refreshInterface {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedObjectID) {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Unit *unit =
        (Unit*)[cdh.context existingObjectWithID:self.selectedObjectID
                                           error:nil];
        self.nameTextField.text = unit.name;
    }
}
- (void)viewDidLoad {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidLoad];
    [self hideKeyboardWhenBackgroundIsTapped];
    self.nameTextField.delegate = self;
}
- (void)viewWillAppear:(BOOL)animated {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self refreshInterface];
    [self.nameTextField becomeFirstResponder];
}
- (void)viewDidDisappear:(BOOL)animated {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                        object:nil
                                                      userInfo:nil];
}

#pragma mark - TEXTFIELD
- (void)textFieldDidEndEditing:(UITextField *)textField {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    Unit *unit =
    (Unit*)[cdh.context existingObjectWithID:self.selectedObjectID
                                       error:nil];
    if (textField == self.nameTextField) {
        unit.name = self.nameTextField.text;
        [[NSNotificationCenter defaultCenter]
         postNotificationName:@"SomethingChanged"
         object:nil];
    }
}

#pragma mark - INTERACTION
- (IBAction)done:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self hideKeyboard];
    [self.navigationController popViewControllerAnimated:YES];
}
- (void)hideKeyboardWhenBackgroundIsTapped {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    UITapGestureRecognizer *tgr =
    [[UITapGestureRecognizer alloc] initWithTarget:self
                                            action:@selector(hideKeyboard)];
    [tgr setCancelsTouchesInView:NO];
    [self.view addGestureRecognizer:tgr];
}
- (void)hideKeyboard {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self.view endEditing:YES];
}
@end

CoreDataPickerTF 此VC是textField的subclass, 并且定义两个dalegate, 供itemvc调用数据时用;

CoreDataPickerTF

#import <UIKit/UIKit.h>
#import "CoreDataHelper.h"
@class CoreDataPickerTF;
@protocol CoreDataPickerTFDelegate 
- (void)selectedObjectID:(NSManagedObjectID*)objectID
      changedForPickerTF:(CoreDataPickerTF*)pickerTF;
@optional
- (void)selectedObjectClearedForPickerTF:(CoreDataPickerTF*)pickerTF;
@end

@interface CoreDataPickerTF : UITextField
<UIKeyInput, UIPickerViewDelegate, UIPickerViewDataSource>
@property (nonatomic, weak) id  pickerDelegate;
@property (nonatomic, strong) UIPickerView *picker;
@property (nonatomic, strong) NSArray *pickerData;
@property (nonatomic, strong) UIToolbar *toolbar;
@property (nonatomic) BOOL showToolbar;
@property (nonatomic, strong) NSManagedObjectID *selectedObjectID;
@end

#import "CoreDataPickerTF.h"
@implementation CoreDataPickerTF
#define debug 0

#pragma mark - DELEGATE & DATASOURCE: UIPickerView
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [self.pickerData count];
}
- (CGFloat)pickerView:(UIPickerView *)pickerView
rowHeightForComponent:(NSInteger)component {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return 44.0f;
}
- (CGFloat)pickerView:(UIPickerView *)pickerView
    widthForComponent:(NSInteger)component {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return 280.0f;
}
- (NSString *)pickerView:(UIPickerView *)pickerView
             titleForRow:(NSInteger)row
            forComponent:(NSInteger)component {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [self.pickerData objectAtIndex:row];
}
- (void)pickerView:(UIPickerView *)pickerView
      didSelectRow:(NSInteger)row
       inComponent:(NSInteger)component {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSManagedObject *object = [self.pickerData objectAtIndex:row];
    [self.pickerDelegate selectedObjectID:object.objectID
                       changedForPickerTF:self];
}

#pragma mark - INTERACTION
- (void)done {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self resignFirstResponder];
}
- (void)clear {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self.pickerDelegate selectedObjectClearedForPickerTF:self];
    [self resignFirstResponder];
}

#pragma mark - DATA
- (void)fetch {
    [NSException raise:NSInternalInconsistencyException format:
     @"You must override the '%@' method to provide data to the picker",
     NSStringFromSelector(_cmd)];
}
- (void)selectDefaultRow {
    [NSException raise:NSInternalInconsistencyException format:
     @"You must override the '%@' method to set the default picker row",
     NSStringFromSelector(_cmd)];
}

#pragma mark - VIEW
- (UIView *)createInputView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self.picker = [[UIPickerView alloc] initWithFrame:CGRectZero];
    self.picker.showsSelectionIndicator = YES;
    self.picker.autoresizingMask = UIViewAutoresizingFlexibleHeight;
    self.picker.dataSource = self;
    self.picker.delegate = self;
    [self fetch];
    return self.picker;
}
- (UIView *)createInputAccessoryView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self.showToolbar = YES;
    if (!self.toolbar && self.showToolbar) {
        self.toolbar = [[UIToolbar alloc] init];
        self.toolbar.barStyle = UIBarStyleBlackTranslucent;
        self.toolbar.autoresizingMask = UIViewAutoresizingFlexibleHeight;
        [self.toolbar sizeToFit];
        CGRect frame = self.toolbar.frame;
        frame.size.height = 44.0f;
        self.toolbar.frame = frame;
        UIBarButtonItem *clearBtn = [[UIBarButtonItem alloc]
                                     initWithTitle:@"Clear"
                                     style:UIBarButtonItemStyleBordered
                                     target:self
                                     action:@selector(clear)];
        UIBarButtonItem *spacer = [[UIBarButtonItem alloc]
                                   initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
                                   target:nil
                                   action:nil];
        UIBarButtonItem *doneBtn =[[UIBarButtonItem alloc]
                                   initWithBarButtonSystemItem:UIBarButtonSystemItemDone
                                   target:self
                                   action:@selector(done)];
        NSArray *array =
        [NSArray arrayWithObjects:clearBtn, spacer, doneBtn, nil];
        [self.toolbar setItems:array];
    }
    return self.toolbar;
}
-  (id)initWithFrame:(CGRect)aRect {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self = [super initWithFrame:aRect]) {

        self.inputView = [self createInputView];
        self.inputAccessoryView = [self createInputAccessoryView];
    }
    return self;
}
- (id)initWithCoder:(NSCoder*)aDecoder {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self = [super initWithCoder:aDecoder]) {
        self.inputView = [self createInputView];
        self.inputAccessoryView = [self createInputAccessoryView];
    }
    return self;
}
- (void)deviceDidRotate:(NSNotification*)notification {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self.picker setNeedsLayout];
}

@end

UnitPickerTF subclass of CoreDataPickerTF

难点: enumerateObjectsUsingBlock

#import "CoreDataPickerTF.h"

@interface UnitPickerTF : CoreDataPickerTF

- (void)fetch;

@end

#import "UnitPickerTF.h"
#import "CoreDataHelper.h"
#import "AppDelegate.h"
#import "Unit.h"
@implementation UnitPickerTF
#define debug 0
- (void)fetch {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSFetchRequest *request =
    [NSFetchRequest fetchRequestWithEntityName:@"Unit"];
    NSSortDescriptor *sort =
    [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
    [request setSortDescriptors:[NSArray arrayWithObject:sort]];
    [request setFetchBatchSize:15];
    NSError *error;
    self.pickerData = [cdh.context executeFetchRequest:request
                                                 error:&error];
    if (error) {
        NSLog(@"Error populating picker: %@, %@"
              , error, error.localizedDescription);}
    [self selectDefaultRow];
}
- (void)selectDefaultRow {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedObjectID && [self.pickerData count] > 0) {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Unit *selectedObject =
        (Unit*)[cdh.context existingObjectWithID:self.selectedObjectID
                                           error:nil];
        [self.pickerData enumerateObjectsUsingBlock:^(
                                                      Unit *unit, NSUInteger idx, BOOL *stop) {
            if ([unit.name compare:selectedObject.name] == NSOrderedSame) {
                [self.picker selectRow:idx inComponent:0 animated:NO];
                [self.pickerDelegate selectedObjectID:self.selectedObjectID
                                   changedForPickerTF:self];
                *stop = YES;
            }
        }];
    }
}
- (NSString *)pickerView:(UIPickerView *)pickerView
             titleForRow:(NSInteger)row
            forComponent:(NSInteger)component {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    Unit *unit = [self.pickerData objectAtIndex:row];
    return unit.name;
}
@end

If the user modifies items during the import process, there is the potential that the same object could be modified in two contexts at the same time. When one of those contexts is then saved, a merge conflict could result. To handle merge conflicts, a merge policy will be configured in advance. The merge policy decides who wins when these conflicts arise. There are five options:

NSErrorMergePolicy is the default policy. Merge conflicts prevent the context from being saved.

NSMergeByPropertyObjectTrumpMergePolicy resolves merge conflicts using object
property values from the context to overwrite those in the persistent store.
NSMergeByPropertyStoreTrumpMergePolicy resolves merge conflicts using object property values from the persistent store to overwrite those in the context.
NSOverwriteMergePolicy resolves merge conflicts using entire objects from the context to overwrite those in the persistent store.
NSRollbackMergePolicy resolves merge conflicts using entire objects from the persistent store to overwrite those in the context.

用代码创建一个CoreData

下图描绘了被管理对象上下文(Managed Object Context)包含2个被管理对象(Managed Object),对应着数据库中2条记录。其中一个对象的属性值已经在内存中更新过了,但还没有提交到数据库保存。另外,还有2条记录在数据库中,并没有对应的被管理对象(Managed Object)。
Managed-Object-Context

一个实体描述对象(Entity Description Object)描述了数据库中一个数据表实体,包括表名称、表对应的被管理对象的名称、属性(Attributes)和关系(Relationships)等等。

下图描绘了实体描述对象、数据库中数据表和被管理对象(对应数据库表的一条记录)之间的关系。
Managed-Object-Model

从上图可以看出,每一个被管理对象(Managed Object)都有对应实体对象的引用。
Core Data使用模型(Model)来映射应用程序中被管理对象和数据库中的数据记录。需要注意的是,如果我们修改数据库的Schema,Core Data将无法使用之前创建的模型来读取数据库记录了。很多持久化机制都存在这个问题。

advanced_persistent_stack

NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *storeDatabaseURL = [url URLByAppendingPathComponent:@"ShoppingCart Database"];
// url is now /ShoppingCart Database
NSError *error = nil;
NSPersistentStoreCoordinator *persistentStoreCoordinator =
[[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]];
if(![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeDatabaseURL options:nil error:&error])
{
NSLog(@"Error while loading persistent store ...%@", error);
}

_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:persistentStoreCoordinator];

相关文章: core data创建shopping app

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

@interface CoreDataHelper :NSObject

@property (nonatomic, readonly) NSManagedObjectContext       *context;
@property (nonatomic, readonly) NSManagedObjectModel         *model;
@property (nonatomic, readonly) NSPersistentStoreCoordinator *coordinator;
@property (nonatomic, readonly) NSPersistentStore            *store;

- (void)setupCoreData;
- (void)saveContext;
@end

CoreDataHelper.m

#import "CoreDataHelper.h"

@implementation CoreDataHelper
#define debug 1

#pragma mark - FILES
NSString *storeFilename = @"Grocery-Dude.sqlite";

#pragma mark - PATHS
- (NSString *)applicationDocumentsDirectory {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class,NSStringFromSelector(_cmd));
    }
    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) lastObject];
}
- (NSURL *)applicationStoresDirectory {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    NSURL *storesDirectory =
    [[NSURL fileURLWithPath:[self applicationDocumentsDirectory]]
     URLByAppendingPathComponent:@"Stores"];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:[storesDirectory path]]) {
        NSError *error = nil;
        if ([fileManager createDirectoryAtURL:storesDirectory
                  withIntermediateDirectories:YES
                                   attributes:nil
                                        error:&error]) {
            if (debug==1) {
                NSLog(@"Successfully created Stores directory");}
        }
        else {NSLog(@"FAILED to create Stores directory: %@", error);}
    }
    return storesDirectory;
}
- (NSURL *)storeURL {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[self applicationStoresDirectory]
            URLByAppendingPathComponent:storeFilename];
}

#pragma mark - SETUP
- (id)init {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self = [super init];
    if (!self) {return nil;}

    _model = [NSManagedObjectModel mergedModelFromBundles:nil];
    _coordinator = [[NSPersistentStoreCoordinator alloc]
                    initWithManagedObjectModel:_model];
    _context = [[NSManagedObjectContext alloc]
                initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_context setPersistentStoreCoordinator:_coordinator];
    return self;
}
- (void)loadStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (_store) {return;} // Don’t load store if it's already loaded

    NSDictionary *options =
  @{
    NSMigratePersistentStoresAutomaticallyOption:@YES ,NSInferMappingModelAutomaticallyOption:@NO ,NSSQLitePragmasOption: @{@"journal_mode": @"DELETE"}
    };

    NSError *error = nil;
    _store = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                        configuration:nil
                                                  URL:[self storeURL]
                                              options:options error:&error];
    if (!_store) {NSLog(@"Failed to add store. Error: %@", error);abort();}
    else         {if (debug==1) {NSLog(@"Successfully added store: %@", _store);}}
}
- (void)setupCoreData {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self loadStore];
}

#pragma mark - SAVING
- (void)saveContext {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if ([_context hasChanges]) {
        NSError *error = nil;
        if ([_context save:&error]) {
            NSLog(@"_context SAVED changes to persistent store");
        } else {
            NSLog(@"Failed to save _context: %@", error);
        }
    } else {
        NSLog(@"SKIPPED _context save, there are no changes!");
    }
}

@end

#import "AppDelegate.h"
#import "Item.h"
#import "Amount.h"

@implementation AppDelegate
#define debug 1

- (void)demo {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    /*
    NSArray *newItemNames = [NSArray arrayWithObjects:
                             @"Apples", @"Milk", @"Bread", @"Cheese", @"Sausages", @"Butter", @"Orange Juice", @"Cereal", @"Coffee", @"Eggs", @"Tomatoes", @"Fish", nil];
    for (NSString *newItemName in newItemNames) { Item *newItem =
        [NSEntityDescription insertNewObjectForEntityForName:@"Item" inManagedObjectContext:_coreDataHelper.context];
        newItem.name = newItemName;
        NSLog(@"Inserted New Managed Object for '%@'", newItem.name); }

     for (int i = 1; i < 5000; i++) {      Meansure *newMeasurement =      [NSEntityDescription insertNewObjectForEntityForName:@"Meansure"      inManagedObjectContext:_coreDataHelper.context];      newMeasurement.abc =      [NSString stringWithFormat:@"-->> LOTS OF TEST DATA x%i",i]; NSLog(@"Inserted %@",newMeasurement.abc);
     }
     [_coreDataHelper saveContext];
   */

    NSFetchRequest *request =
    [NSFetchRequest fetchRequestWithEntityName:@"Amount"]; [request setFetchLimit:50];
    NSError *error = nil;
    NSArray *fetchedObjects =
    [_coreDataHelper.context executeFetchRequest:request error:&error];
    if (error) {NSLog(@"%@", error);} else {
        for (Amount *measurement in fetchedObjects) { NSLog(@"Fetched Object = %@", measurement.xyz);
        } }

}

- (CoreDataHelper*)cdh {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (!_coreDataHelper) {
        _coreDataHelper = [CoreDataHelper new];
        [_coreDataHelper setupCoreData];
    }
    return _coreDataHelper;

}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Override point for customization after application launch.
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    [[self cdh] saveContext];
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

    [self cdh];
    [self demo];
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    [[self cdh] saveContext];
}

@end

IOS7-CoreDAta系列: attribute数据类型详情

基于计算的,integer的速度会比float快

 Integer 16 ranges from –32768 to 32767
■ Integer 32 ranges from –2147483648 to 2147483647
■ Integer 64 ranges from –9223372036854775808 to 9223372036854775807

Integers use the base-2 number system, better known as binary. Calculations using integers are faster than those with floating-point numbers

Float and Double

The biggest float on iOS is 340282346638528859811704183484516925440.000000. Floats and doubles are also stored with a sign bit, so this means that the lowest possible float on iOS is –340282346638528859811704183484516925440.000000. The biggest double is much larger than the biggest float. 

Decimal十进制

Decimal is the recommended attribute data type for working with money and other scenarios where base-10 arithmetic is appropriate. 

图片,音频,等用Binary Data

Binary Data
If you need to store photos, audio, or some other contiguous BLOB of zeros and ones, you should use the Binary Data attribute type. 

Transformable

The Transformable attribute data type is used to store an Objective-C object. This attribute type is a flexible option that allows you to store an instance of any class. An example of a transformable attribute is an instance of UIColor. When an NSManagedObject subclass is created from an entity containing a transformable attribute, the resulting property type is id. For the id object to make it into the store (and back again), you’ll need to use an instance of NSValueTransformer or an instance of a subclass of NSValueTransformer. This class helps transparently convert the attribute to NSData and back again.