Save date object to core data - ios

I have Core Data and one of my entity property is object of type Date (i assume it is NSDate).
From JSON response i got date in following format:2016-01-09 22:33:33
When mapping, i'm setting values in my NSManagedObject like that:
[item setValue:[obj valueForKey:JS_DATE] forKey:CD_DATE];
Where macros are simply string, referring to entity name or json response key for date.
App throw an exception on that, i assume i want to save NSDate with different format. How to fix that?
Thanks.

Try something along the lines of
// Setup formatter
myDateFormatter = [[NSDateFormatter alloc] init];
myDateFormatter.locale = [NSLocale localeWithLocaleIdentifier:#"en_US_POSIX"];
myDateFormatter.dateFormat = #"yyyy-MM-dd' 'HH:mm:ss";
myDateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
// Use it
NSString *stringFromJSON = #"2016-01-09 22:33:33";
NSDate *date = [myDateFormatter dateFromString:stringFromJSON];
// Validate Date
NSLog(#"Date: %#, date);
You probably have to experiment with locale and timeZone, depending on where you live. Have a look at that tech note for hints: https://developer.apple.com/library/content/qa/qa1480/_index.html

Related

Sorting NSMutableArray based on the content of its objects

I have an NSMutableArray called self.objectArray, that contains custom objects. Each object holds an NSDictionary and two other string objects. Actually I need to work only with the dictionary. Every dictionary contains a key named keyDate which holds an NSString that look like this: MM/dd/yy HH:mm:ss.
I would like to sort the array based on their keyDate. The object with the oldest date should be the first object and so on. I've found some questions, that looked helpful and I could create the code that you can see below, but I get an error everytime I run it. As I think NSSortDescriptor won't be the right tool since my keys aren't key value compliant.
PNMessage 0x125c0590> valueForUndefinedKey:]: this class is not key value coding-compliant for the key keyDate.'
NSSortDescriptor *dateDescriptor = [NSSortDescriptor
sortDescriptorWithKey:#"keyDate"
ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:dateDescriptor];
NSArray *sortedEventArray = [self.objectArray
sortedArrayUsingDescriptors:sortDescriptors];
self.finallySorted = [sortedEventArray mutableCopy];
If it's possible I would do it with sort descriptor, however I think there should be some other options, but can't figure out its proper implementation.
So I can also catch every object's keyDate with a for loop, but don't know how can I sort them based on the value. I would really appreciate if somebody could show me the right way.
for(PNMessage *mg in self.objectArray)
{
NSLog(#" test log %#", mg.message[#"keyDate"]);
}
I already checked this answer:
How to sort an NSMutableArray with custom objects in it?
but the structure of my object is different.
My first code based on this question, but it doesn't worked.
How to sort an NSMutableArray with custom objects in it?
UPDATE: my try based on Kaan's answer (doesn't works yet)
static NSDateFormatter *formatter = nil;
if(!formatter) {
formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MM/dd/yy HH:mm:ss"];
}
NSArray *sortedArray = [self.object sortedArrayUsingComparator:^NSComparisonResult(PNMessage *obj1, PNMessage *obj2) {
NSString *date1String = obj1.message[#"keyDate"];
NSString *date2String = obj1.message[#"keyDate"];
NSDate *date1 = [formatter dateFromString:date1String];
NSDate *date2 = [formatter dateFromString:date2String];
if ( date1 < date2 ) {
return (NSComparisonResult)NSOrderedAscending;
} else if ( date1 > date2 ) {
return (NSComparisonResult)NSOrderedDescending;
}
return (NSComparisonResult)NSOrderedSame;
}];
I would consider using the sortedArrayUsingComparator method
Assuming your custom class is called PNMessage:
static NSDateFormatter *formatter = nil;
if(!formatter) {
formatter = [[NSDateFormatter alloc] init];
[formatter setFormat:#"MM/dd/yy HH:mm:ss"];
}
NSArray *sortedArray = [self.objectArray sortedArrayUsingComparator:^NSComparisonResult(PNMessage *obj1, PNMessage *obj2) {
NSString *date1String = obj1[#"keyDate"];
NSString *date2String = obj1[#"keyDate"];
NSDate *date1 = [formatter dateFromString:date1String];
NSDate *date2 = [formatter dateFromString:date2String];
return [date1 compare:date2];
}];
Tip: If you decide on following this, make sure you declare your NSDateFormatter instance as static outside of the sorting body, since allocating Formatters in iOS can be very expensive and cause serious performance penalties.

NSDate Locale Causing Issues for Other Locales when Using NSDateFormatter with NSString and NSDate

I have a working app right now but I have hit a big stumbling block. The app has the premise of a tab bar controller (TB) which has two Tabs consisting of one table view in each tab. Table view 1 (TV1) is populated by the user clicking the plus button in the navigation bar and filling in the name and event text fields and selecting a date from the date picker. Table view 2 is an illustration of each unique event that the user has created. Where table 1 shows every transaction in chronological order, table 2 shows only the unique event names so you can view the transactions by events.
I'm using Core Data and NSFetchedResultsControllers to tie everything together.
This is working very well but I'm looking to introduce an update.
If the user clicks on the Table 2 and sees the events showing for example, Anniversary, Birthday and Wedding, and then clicks on say, Birthday, they will see all of the transactions where event = birthday (selected events scene). I've put a new navigation bar button in here (selected events) to allow the user to add a new entry with the date and event already populated from the selected event.
I have this 95% working. The issue comes down to NSLocales and Date Formatters.
I'll put some code here and then talk about the issue:
In the Add Entry, the NSCalendar for the date and datePicker is:
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
// Will explain the commented out lines
//NSLocale *brLocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
//[cal setLocale:brLocale];
NSDateComponents *components = [cal components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit
fromDate:self.datePicker.date];
[components setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSDate *selectedDate = [cal dateFromComponents:components];
Before this update, I did not have the locale (Commented out code) in this code because it would just use the device local and that worked very well. Now I had to add it in to get the next part working.
In the Selected Event Controller, the prepareForSegue is:
if ([segue.identifier isEqualToString:#"Create New Entry From Event"])
{
AddEntryViewController *addEntryViewController = (AddEntryViewController *)segue.destinationViewController;
[addEntryViewController setSelectedEvent:self.occasion.title];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0] ;
NSString *sectionTitle = [self tableView:self.selectedOccasionTableView titleForHeaderInSection:indexPath.section];
NSLog(#"The section header is: %#", sectionTitle);
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MMMM d, yyyy"];
NSDate *dateFromString = [[NSDate alloc] init];
dateFromString = [dateFormatter dateFromString:sectionTitle];
NSLog(#"The new date is %#", [dateFormatter stringFromDate:dateFromString]);
[addEntryViewController setSelectedDate:dateFromString];
[addEntryViewController setIsFromSelectedEvent:YES];
}
I'm getting the date from the section title and sending that over to the Add Entry where:
- (void)setSelectedDate:(NSDate *)selectedDate
{
_selectedDate = selectedDate;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.occasionTextField.text = self.selectedEvent;
if (self.isFromSelectedEvent)
{
NSLog(#"This gets run");
[self.datePicker setDate:self.selectedDate animated:YES];
}
}
The issue I'm having is - the app crashes if the device locale is set to anything but the US locale (with the code not commented out above). This is not desirable of course.
A LOT of effort has gone into getting the app working with the dates in this format and I cannot change that because a lot is dependent on the dates in the section headers.
I need a way to adjust the NSDateFormatter code in the prepareForSegue to take into account whatever format the device is using.
The code below is converting the NSDate into an NSString so it's readable in the section title, but here is where the problem is.
-(NSString *)sectionDateFormatter
{
return [NSDateFormatter localizedStringFromDate:self.dates.dateOfEvent
dateStyle:NSDateFormatterLongStyle
timeStyle:NSDateFormatterNoStyle];
}
If the user is using an American locale, the date will appear as Month Date, Year. If the user is using a UK locale it'll be Date Month Year.
I need to somehow fix this. I either need to:
1) Set the locale of the NSDate/NSString to one particular format regardless of locale, or
2) Manipulate the code to support all potential locales.
Number 1 seems to be easier but I have no idea how to achieve this. I need this app to not crash if you're using it of course.
If you're using a UK locale, it crashes with:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: date'
I need a way to stick to one format regardless of where you are. For example, I would love to set the format to be "Date Month, Year" regardless of whether you're in America and using an American locale, or in Singapore using a Singaporean Locale.
Any thoughts in the right direction would be seriously appreciated here.
To control the date format in depend of the current locale setting define a formats instead of using the date/time style. For example if you use
[dateFormatter setDateFormat:#"MMMM dd, yyyy"];
Or any other format you want to display. The you can create the string object using this date formatter
[dateFormatter stringFromDate:self.dates.dateOfEvent];
Read up on various format strings here: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/DataFormatting/Articles/dfDateFormatting10_4.html#//apple_ref/doc/uid/TP40002369-SW1

Changing Core Data Attribute from NSString to NSDate and getting this functionality to work using a Date Picker

I am working through my first app and need some advice on how to approach this next task and issue. The premise of the app is the user has a table view, clicks on a plus button in the navigation bar and is presented with text fields to insert information. Upon clicking on save, that gets saved to the core data and displayed in the table view.
The table view is sectioned. Right now, I have the "date" being represented as a NSString, just to get my app off the ground, but I need to change this to a DatePicker. The sections' are based on the Dates.
I have a Core Data Model as follows:
Transaction Entity
Person Entity
Occasion Entity
Date Entity
The Transaction Entity has a relationship to each of the other entities here.
As mentioned, at first, to get my app working and off the ground, I made the Date Entity have a dateOfEvent attribute which was a NSString rather than a NSDate but of course that will not work in the long run.
I have changed my model to NSDate for this attribute and regenerated the NSManagedObject Subclasses.
Independently, I have a DatePicker working without any issues but it outputs the information to a String in a textfield.
What I want to achieve now is to use the DatePicker, select a date and have that saved to the Core Data Date Entity (dateOfEvent attribute) which I can then use in the table view as the section titles.
Here is my code for saving in the view controller:
- (IBAction)save:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
Transaction *transaction = [NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:context];
Date *enteredDate = (Date *)[Date occasionWithDate:self.dateTextField.text inManagedObjectContext:context];
transaction.dates = enteredDate;
// Code to save Person, Occasion, etc.
}
The enteredDate is calling a specific occasionWithDate method:
+ (Date *)occasionWithDate:(NSString *)enteredDate inManagedObjectContext:(NSManagedObjectContext *)context
Date *date = nil;
// Creating a fetch request to check whether the name of the person already exists
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Date"];
request.predicate = [NSPredicate predicateWithFormat:#"dateOfEvent = %#", enteredDate];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"dateOfEvent" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *dates = [context executeFetchRequest:request error:&error];
if (!dates)
{
// Handle Error
}
else if (![dates count])
{
// If the person count is 0 then let's create it
date = [NSEntityDescription insertNewObjectForEntityForName:#"Date" inManagedObjectContext:context];
date.dateOfEvent = enteredDate;
}
else
{
// If the object exists, just return the last object .
date = [dates lastObject];
}
return date;
}
This does a fetchRequest to ensure I am either returning an existing date or adding a new one if that does not exist.
That is the behaviour I would like here, but of course, that method is passing a String and I need to pass a date.
With this in mind, how do I go about selecting the value of the DatePicker, adding it to the Core Data database in the same way as above (checking whether the date exists) and having this displayed in the sections of the Table View?
The reason I want to check if the date exists is because if there is an event on the 2nd December 2013, it'll be unique. However if I create another event on the 2nd December 2013, I'd want it to use the existing 2nd December, rather than create a second entry for 2nd December. The reason is my app has a tab view where the second tab is predicated by dates and so I would not want two separate 2nd December there.
This is a side note. The main thing I would like to achieve is, use the Date Picker and save the selected value to Transaction.dates.dateOfEvent to Core Data.
I know if I were to do something like date.dateOfEvent = [NSDate date]; it would be assigning the date and time now. That is not what I want here.
Any assistance would be massively appreciated.
Thanks,
EDIT: Adding in UIDatePicker Code - this first code snippet below is for saving to the textField when using NSString as the attribute
In viewDidLoad
[self.datePicker addTarget:self action:#selector(getSelection:) forControlEvents:UIControlEventValueChanged];
-(void)getSelection:(id)sender
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterLongStyle];
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
NSDate *date = [self.datePicker date];
NSString *formattedDateString = [dateFormatter stringFromDate:date];
self.dateTextField.text = formattedDateString;
}
Edit: The save to core data method is above - it calls occasionWithDate method and checks if the date exists already when the dateOfEvent attribute is NSString. Because I need to sort by ascending dates in the table view, I have changed the dateOfEvent to be a NSDate format
To get the current date and time, I'm putting this code in the save method:
Date *date = [NSEntityDescription insertNewObjectForEntityForName:#"Date" inManagedObjectContext:context];
date.dateOfEvent = [NSDate date];
transaction.dates = date;
That is giving me the current date and time. What I want is for the user to select a date using the UIDatePicker and whatever date is selected, for that to be saved as the dateOfEvent attribute of the Date entity which I can then use in the Sections of the Table view.
To get NSDate from your UIDatePicker object use UIDatePicket date property.
date
The date displayed by the date picker.
#property(nonatomic, retain) NSDate *date
Discussion
The default is the date when the UIDatePicker object is created. The date is ignored in the mode UIDatePickerModeCountDownTimer; for that mode, the date picker starts at 0:00. Setting this property does not animate the date picker by spinning the wheels to the new date and time; to do that you must use the setDate:animated: method.
check Apple UIDatePicker documentation

Core Data. Get entity with max "dateTime" which is written as string?

Currently I get all the entities of this type and search for max value via for-each cycle.
I use NSFetchRequest with NSPredicate as param.
Is it possible to find this entity via one core data request only? As I understand this request should contain a conversion to a date and date comparison.
Yes, you can, but you'll need a trick somewhere.
If the string format would be like yyyy/MM/dd HH:mm:ss you could do this easily with a sort descriptor. Since you are using the format MM/dd/yyyy this will not work, since it will sort with months first.
However, you can create a category on your managed object which adds an NSDate property, which dynamically converts your string dates into real dates. In your sort descriptor you can then use this category field, sort descending and pick the first result.
EDIT: NSSortDescriptors cannot be used with transient properties, so this is not an option. (See also Martin R's comment below).
Another option would be to add an extra date field to your entity, which stores the same date in real date-format. Therefore you would need to update your current database model, but this would probably perform slightly better.
To clarify the last option:
(Assuming you have added a date field called: 'realDateTime')
When storing the entities you can add the following code:
// String to date conversion
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MM/dd/yyyy HH:mm:ss"];
realDateTime = [dateFormatter dateFromString:dateTime];
To find the entity with the most recent date, you can implement a method which looks something like this:
- (NSManagedObject *)findObjectMostRecentDate {
// Set a sort descriptor to set last date as first result
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:#"realDateTime" ascending:NO]]];
// Execute the fetch request
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
if (results.count) {
return [results firstObject]; // <-- This returns the object with the 'max' date
else
return nil; // No objects fetched
}

Where is an appropriate place to put Application wide date formatting in iOS?

In a typical iOS application, where should one put application wide objects? I'd like to centralized my Date formatting code and I would be interested in hearing suggestions on best practices for doing so.
For example, I have the following code to do date formatting:
NSLocale *usLocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
NSString *usFormatString = [NSDateFormatter dateFormatFromTemplate:#"EEE, MMM d YYY" options:0 locale:usLocale];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = usFormatString;
((UILabel *)[selectedCell.contentView viewWithTag:1]).text = [formatter stringFromDate:date];
I'd like to keep the formatting code as DRY as possible.
EDIT: This turned out to be a multipart answer and I took the approach of using a combination of a Singleton and creating a Category for NSString. I upvoted most of you, but I accepted #Jack_Lawrence.
I like using Objective-C categories for that sort of stuff. Categories are good for extending the abilities of existing objects, especially objects from frameworks that you don't have control over. Make sure to prefix method names so you don't conflict with current/future methods Apple may implement.
In this specific case, I would create a category on NSDate called NSDate+DateFormatting and implement a method that returns an NSString from the date receiver:
- (NSString *)JL_stringByFormattingDate
{
NSLocale *usLocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
NSString *usFormatString = [NSDateFormatter dateFormatFromTemplate:#"EEE, MMM d YYY" options:0 locale:usLocale];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = usFormatString;
return [formatter stringFromDate:self.date];
}
For things like this I would always suggest using singletons rather than putting functions in the app delegate.
The below article has a really good overview of how to use singletons , and their advantages.
http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html
Usually "application wide" objects can be referenced via a property that you add to your application's delegate.
So create a NSDateFormatter and assign it to a property that you can reference from your delegate via something like:
NSDateFormatter * myAppDateFormatter = nil;
MyFineAppDelegate * myAppDelegate = (MyFineAppDelegate *)[[UIApplication sharedApplication] delegate];
if(myAppDelegate)
{
myAppDateFormatter = myAppDelegate.dateFormatter;
}
Singleton classes are a possible solution. Basically, wherever you call the object from, it will return the same instance. See here for some more info and a how to.

Resources