Conversion from NSDate to NSString with Core-Data Migration on Attribute Not Displaying the String Appropriately

StackOverflow https://stackoverflow.com/questions/23080608

Question

I'm sorry for the long question; I have no idea how to explain this without including lots of code and images.

I am working on updating my app and because I discovered an issue relating to dates, I had to change an Attribute from NSDate to NSString. I'm using Core Data for my application.

The model of the application is:

Transaction Entity

Date Entity

Years Entity

The Year Entity has an attribute which is called yearOfEvent of type NSDate and with the update to my app (already in the app store), I am changing the yearOfEvent to NSString.

A bit of research and an answer to another question informed me that I have to do the migration myself.

This is mostly working, but is not quite there yet.

My application is a two-tabbed UITabBarController where both Tabs are UITableViewControllers. The first is a "Timeline" of chronological ordered entries. The second tab is supposed to display only the years that have been entered; so if a user has entries with 2014, 2013 and 2012, you'll see those in the cells in the Dates Tab and when you tap on that cell, it shows you the entries with that year. That's what I'm trying to change here. The app is populated by the user adding in some information into UITextFields and picking a date from the UIDatePicker which gets saved to Core-Data and then the Table updates using NSFetchedResultsController.

Problem

Right now, when I have my app from the App Store running with data and then migrate to the new version of my app with the custom migration, the Timeline is fine, but the cells of the Dates TableViewController have the labels showing blank years. The number of cells is correct, but the years are blank.

I followed the accepted answer in this question: Example or explanation of Core Data Migration with multiple passes? with the first bit of the code, but I'm not sure where to put the second part (final part of the code).

So in my Model class, I have:

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
                                      entityMapping:(NSEntityMapping *)mapping
                                            manager:(NSMigrationManager *)manager
                                              error:(NSError **)error
{
    // Create a new object for the model context
    NSManagedObject *newObject =
    [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName]
                                  inManagedObjectContext:[manager destinationContext]];

    // do our transfer of nsdate to nsstring
    NSDate *date = self.transaction.dates.dateOfEvent;
    //NSDate *date = [sInstance valueForKey:@"timeStamp"];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];

    [dateFormatter setDateFormat:@"y"];
    //NSString *stringFromYear = [formatter stringFromDate:trans.years.yearOfEvent];


    // set the value for our new object
    [newObject setValue:[dateFormatter stringFromDate:date] forKey:@"yearOfEvent"];

    // do the coupling of old and new
    [manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];

    return YES;
}

So in my case, I'm extracting the "year" out of the Date Entity because the yearOfEvent is now a string.

Also, I have no idea where to place the rest of the code in the accepted answer with the migration.

For clarity, here's my NSFetchedResultsController which sets the dates:

- (NSFetchedResultsController *)fetchedResultsController
{
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
    if (_fetchedResultsController != nil)
    {
        return _fetchedResultsController;
    }
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Years" inManagedObjectContext:managedObjectContext];
    fetchRequest.entity = entity;
    NSPredicate *d = [NSPredicate predicateWithFormat:@"transactions.years.@count !=0"];
    [fetchRequest setPredicate:d];
    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"yearOfEvent" ascending:NO];

    fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
    fetchRequest.fetchBatchSize = 20;
    NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
    self.fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;
    return _fetchedResultsController;
}

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    EnvylopeDatesTableViewCell  *customCell = (EnvylopeDatesTableViewCell *)cell;
    Years *years = [self.fetchedResultsController objectAtIndexPath:indexPath];
    NSString *stringFromDate = years.yearOfEvent;
    customCell.datesLabel.text = stringFromDate;
    customCell.datesLabel.textColor = [UIColor whiteColor];

    UIView *customColorView = [[UIView alloc] init];
    customColorView.backgroundColor = [UIColor darkGrayColor];
    customCell.selectedBackgroundView = customColorView;

}

In the MappingModel, I've left the default value expression for the YearstoYears entity and made sure to set the Custom Policy to be the Model class I created (Model.h/m).

enter image description here

Any guidance on this would be really appreciated.

Notes - The reason I have a Date and a Year tab was for formatting throughout the app. I'm sure I may be able to get away with having just a Date and then extracting the "year" out of that into an NSString. I will get to do that, but for now, I just want the migration to work appropriately.

  • To reiterate, the problem is, if I am running an older version of the app and I migrate to this newly built version, the Dates tab does not correct set the years and the cells are blank for the year.
Was it helpful?

Solution

I have not undertaken a heavyweight migration before, instead preferring to use lightweight migration. I congratulate you on tackling this detailed process.

There is a book that I highly recommend - from The Pragmatic Bookshelf – "Core Data, 2nd Edition, Data Storage and Management for iOS, OS X, and iCloud" (Jan 2013) by Marcus S. Zarra, and in particular Chapter 3 titled "Versioning and Migration".

Regarding your question about the location for the final part of the code for the answer to this SO question... Example or explanation of Core Data Migration with multiple passes? I suspect the final part of the code from that answer should be placed before you need to add your persistent store (pass the URL for your NSPersistentStore) (in that example the NSURL *destinationStoreURL) to your NSPersistentStoreCoordinator. That is, before the line of code in your Core Data stack that would look something similar to this...

NSPersistentStore *persistentStore = [self.persistentStoreCoordinator
                        addPersistentStoreWithType:NSSQLiteStoreType 
                        configuration:nil 
                        URL:destinationStoreURL 
                        options:options];

Now on to your problem -

the Dates tab does not correct(ly) set the years and the cells are blank for the year

I have two questions...

After your NSFetchResultsController has run, are you able to log (NSLog) the value for fetchedObjects to check whether you are actually receiving any data from your NSFetchedResultsController? It may be that the data conversion is not successful and your FRC is returning nil? For example:

NSLog(@"fetched objects are: %@", self.fetchResultsController.fetchedObjects);

Also, in your configureCell: method, are you able to log the value for your local variable NSString * stringFromDate?

Let me know how you go... whether or not you are receiving data from your NSFetchedResultsController and whether this is successfully passed to the configureCell method.

Once we know the answer to my questions, I can hopefully help you work out a solution.

OTHER TIPS

This is an answer in response to your answer @AndrewBuilder.

I appreciate your answer and my apologies for the late reply. It was a very scary approach doing the custom migration, especially because I'm still fairly new to development. However, I managed to get it fixed with one line of code in addition to the code in my question.

When I went back to the answer I referred to, I noticed that he had a line valueForKey before the conversion and so I just did that:

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
                                      entityMapping:(NSEntityMapping *)mapping
                                            manager:(NSMigrationManager *)manager
                                              error:(NSError **)error
{
    // Create a new object for the model context
    NSManagedObject *newObject =
    [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName]
                                  inManagedObjectContext:[manager destinationContext]];

    // do our transfer of nsdate to nsstring
    NSDate *date = [sInstance valueForKey:@"yearOfEvent"]; 
    //NSDate *date = [sInstance valueForKey:@"timeStamp"];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];

    [dateFormatter setDateFormat:@"y"];
    //NSString *stringFromYear = [formatter stringFromDate:trans.years.yearOfEvent];


    // set the value for our new object
    [newObject setValue:[dateFormatter stringFromDate:date] forKey:@"yearOfEvent"];

    // do the coupling of old and new
    [manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];

    return YES;
}

In my case, the key is yearOfEvent.

Worryingly, or interestingly, that was all I had to do to get this working. When I did that with the yearOfEvent NSDate *date = [sInstance valueForKey:@"yearOfEvent"]; it started working straight away.

I'm skeptical about my approach because I have not actually placed the rest of the code in his answer anywhere in my code, yet the migration works from version 1.0 to 1.2.0, 1.1.1 to 1.2.0 and 1.0 to 1.1.1 to 1.2.0.

I will have to figure out what's going on here!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top