DatePicker stopping CoreData working as expected - iphone

I have an app which saves text and the date from a UIDatePicker and then shows that note if you got back into that date in the UIDatePicker.
It works great! Only I have found that setting the UIDatePicker date to today stops CoreData working.
It's only when I run this setDate line does it stop core data from working. The app runs fine without crashing, it just doesn't save any data. If I comment that line out, it works a charm. But I need to have the UIDatePicker on today when the app loads.
I use this when the application starts:
NSDate *now = [[NSDate alloc] init];
[datePicker setDate:now];
This to fetch the note:
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSEntityDescription *testEntity = [NSEntityDescription entityForName:#"DatedText" inManagedObjectContext:self.managedObjectContext];
[fetch setEntity:testEntity];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"dateSaved == %#", datePicker.date];
[fetch setPredicate:pred];
NSError *fetchError = nil;
NSArray *fetchedObjs = [self.managedObjectContext executeFetchRequest:fetch error:&fetchError];
if (fetchError != nil) {
NSLog(#"fetchError = %#, details = %#",fetchError,fetchError.userInfo);
}
noteTextView.text = [[fetchedObjs objectAtIndex:0] valueForKey:#"savedText"];
And this to save the note:
NSManagedObject *newDatedText;
newDatedText = [NSEntityDescription insertNewObjectForEntityForName:#"DatedText" inManagedObjectContext:self.managedObjectContext];
[newDatedText setValue:noteTextView.text forKey:#"savedText"];
[newDatedText setValue:datePicker.date forKey:#"dateSaved"];
NSError *saveError = nil;
[self.managedObjectContext save:&saveError];
if (saveError != nil) {
NSLog(#"[%# saveContext] Error saving context: Error = %#, details = %#",[self class], saveError,saveError.userInfo);
}

Remember NSDate saves not only DD/MM/YYYY but also HH:MM:SS.
At a guess I think when you pick a DD/MM/YYYY from the picker, it saves with a default time of 0:00:00 but in the case above when you set the picker date to now you are actually manipulating the HH:MM:SS to something else (even though you don't see it manually).
To illustrate what I'm trying to say, when you fetch is with a predicate of (dateSaved == picker.date) it is looking for a date in the format DD/MM/YYYY 00:00:00 and for arguments sake you may have saved it on DD/MM/YYYY 09:00:01.
You will need to do some formatting of your NSDate attribute if you want this to work.

Datepicker is by default set to todays date only. You don't need to do it manually.

Related

Core data: The fetched object at index x has an out of order section name 'xxxxxx. Objects must be sorted by section name

I know I'm not the first to ask this question but I'm really stumped..
Basically I have a screen with two buttons. Each button loads data into a tableview below based on a date. On the first load of the first tableview (the left button is selected by default) everything displays fine. If I click on the right button and I get a blank tableview, and I get the error
The fetched object at index x has an out of order section name
'xxxxxx. Objects must be sorted by section name.
Switching back to the left table view, the data is gone. Both tableviews are empty.
Each tableview has 2 sections depending on the start time of the item. If I eliminate the sections the data displays fine. Unfortunately I need them.. the data is sorted into the two sections like so:
#interface NSString(agendaSessionKeyPath)
#property (nonatomic, readonly) NSString *sessionSection;
#end
#implementation NSString(agendaSessionKeyPath)
- (NSString *)sessionSection
{
int timeValue = [[self stringByReplacingOccurrencesOfString:#":" withString:#""] intValue]; //turns 11:00 to 1100
if (timeValue < 1200)
return #"Morning";
else
return #"Afternoon";
}
Fetch request
- (void)viewDidLoad
{
//other viewDidLoad stuff
[self fetchSessions];
}
method which sorts the data from the left and right button based on date:
- (void)fetchSessions
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
[dateFormatter setDateFormat:#"yyyy-MM-dd"];
NSDate* date = nil;
if (selected == 0) //left button is selected
{
date = [dateFormatter dateFromString:#"2012-09-26"];
}
else if (selected == 1) //right button is selected
{
date = [dateFormatter dateFromString:#"2012-09-27"];
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"date == %#", date];
[self.fetchedResultsController.fetchRequest setPredicate:predicate];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
fetched results controller
- (NSFetchedResultsController *)fetchedResultsController {
self.managedObjectContext = [[MATCDatabaseController sharedDatabaseController] managedObjectContext];
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Session"];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"title" ascending:YES];
NSSortDescriptor *timeSort = [NSSortDescriptor sortDescriptorWithKey:#"timeValue" ascending:YES];
[fetchRequest setSortDescriptors:#[timeSort, sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"startTime.sessionSection"
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
[self.fetchedResultsController setDelegate:self];
return _fetchedResultsController;
}
any help is appreciated!
OK, I did take a quick peek.
You initialize the FRC with:
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"startTime.sessionSection"
cacheName:nil];
which tells it that your section titles are to be obtained via the key path startTime.sessionSection.
Now, the first sort descriptor of a fetch request that is given to a FRC will be used to sort the sections. The sort descriptor you are providing first is for timeValue which does not seem right.
Your first sort descriptor should specify a sort for your section titles. Change that and you may be good to go.
EDIT
Thanks guys for the info. I'm still a bit lost though. Did you mean
that I should add a sort descriptor on startTime.sessionSection before
assigning it to the sectionNameKeyPath? I tried, but still no luck.
timeValue and startTime.sessionSection are related. Could that be it?
– pigeonfactory
You have to make sure that the very first sort descriptor will properly sort your data based on the section. In your case, times are being converted into words. Your initial sort descriptor is for times, and when the data is sorted based on time, the sections are not sorted properly, which is causing your error.
The very first sort descriptor must satisfy the section data. So, initially, I would try...
[fetchRequest setSortDescriptors:#[
[NSSortDescriptor sortDescriptorWithKey:#"startTime.sessionSection"
ascending:NO],
[NSSortDescriptor sortDescriptorWithKey:#"timeValue"
ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"title"
ascending:YES] ];
Note, if you have lots and lots of data, you may find that your section mechanism gets slow. If that happens, you may want to add this section data to your database.
I also have got a similar issue, perhaps someone find it useful.
I fetched financial transactions from DB and sectioned them by Month.
DB has property trDate - which is a transaction date. But trMonth is a transient property like:
- (NSString*)trMonth {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"LLLL"];
[dateFormatter setLocale:[NSLocale currentLocale]];
return [dateFormatter stringFromDate:trDate];
}
It was used in FetchResultsController like this:
...
NSSortDescriptor *sortDate = [[NSSortDescriptor alloc] initWithKey:#"trDate" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDate]];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"trMonth" cacheName:nil];
...
Which worked well in production, but once app started to crash, and after discovering the root issue I figured out that there two transaction for (for instance) on July: July 2014 and July 2015. That was the crash point as trDate sorted well July 2014 < July 2015, but my method considered them as the same month. The solution was use as section names not only the month name but year also:
- (NSString*)trMonthYear {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"LLLL yyyy"]; // added year also
[dateFormatter setLocale:[NSLocale currentLocale]];
return [dateFormatter stringFromDate:trDate];
}

returning a stringfromdate method

So here's some code that I'm having trouble with:
//format the date to a string for echoing it
NSDateFormatter* formattedDate = [[NSDateFormatter alloc] init];
[formattedDate setDateStyle:NSDateFormatterLongStyle]; //now myFormatted is set to a long style
NSString* dateForOutput = [formattedDate stringFromDate:self.datePickerOutlet.date];
//also now need to set the "you began on" text to the newly chosen date
[self.startDate setText:#"You started on: %#", dateForOutput];
The error that is given is: "Too Many Arguments to method call, expected 1, have 2"
I don't see why it's saying that I'm trying to pass in two methods.
I tried to do the following in case I was being stupid but it still gave me an error:
//format the date to a string for echoing it
NSDateFormatter* formattedDate = [[NSDateFormatter alloc] init];
[formattedDate setDateStyle:NSDateFormatterLongStyle]; //now myFormatted is set to a long style
NSString* dateForOutput = [formattedDate stringFromDate:self.datePickerOutlet.date];
//also now need to set the "you began on" text to the newly chosen date
NSString *foobar = #"You started on: %#", dateForOutput;
[self.startDate setText:foobar];
Error given: "Interface type cannot be statically allocated"
Frankly I have no idea why it's giving me this error... some help would be greatly appreciated.
It's probably just something small that I'm just not seeing for some reason =/
cheers,
Matt
Instead of the line
[self.startDate setText:#"You started on: %#", dateForOutput];
in the first block of code you have given, try the following line
[self.startDate setText:[NSString stringWithFormat:#"You started on: %#", dateForOutput]];
But it is better to go with the second statements,
NSString *foobar = [NSString stringWithFormat:#"You started on: %#", dateForOutput];
you are doing things in wrong way
You should do the Things in this way
NSDate* myDate=[NSDate new];
NSDateFormatter* formattedDate = [[NSDateFormatter alloc] init];
[formattedDate setDateStyle:NSDateFormatterLongStyle];
//now myFormatted is set to a long style
// here i just passed the current date
NSString* dateForOutput = [formattedDate stringFromDate:myDate];
//also now need to set the "you began on" text to the newly chosen date
NSString *foobar = #"You started on:";
//Now you can append The String
//this is the way you can append the string ..
foobar= [foobar stringByAppendingFormat:#"%#",dateForOutput];
[self.startDate setText:foobar];

How do I get the last record of a Core Data database?

I have a core data entity called images that has just 2 fields:
imageName = NSString
timeStamp = NSNumber
I am trying to simulate a kind of stack LIFO (last in first out).
Inserting a new entry is easy but what about reading the last entry added to the entity?
All images are added with a timestamp, obtained by using
time_t unixTime = (time_t) [[NSDate date] timeIntervalSince1970];
an integer that is equal to the number of seconds since 1970
so, how do I retrieve the last inserted record of a core data (= the record that has the biggest timestamp number)???
thanks
Perform a fetch request, sorting the results by timeStamp.
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:...];
// Results should be in descending order of timeStamp.
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"timeStamp" ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
NSArray *results = [managedObjectContext executeFetchRequest:request error:NULL];
Entity *latestEntity = [results objectAtIndex:0];
You might also want to restrict the number of results using NSFetchRequest's setFetchLimit:.
I have tried using the method that Chris Doble mentioned and found it to be very slow, especially if there are lot of records that would need to be pulled and checked against the timeStamp. If you want to speed things up, I am now setting an attribute called isMostRecent on my ManagedObject's that I may ever want to get the most recent from. When a new record is to be stored I just grab the most recent record that has this attribute set to YES and change it to NO then set the new record that is being stored to YES. The next time I need to grab to most recent record all I have to do is this...
+ (Photo*)latestPhotoForMOC:(NSManagedObjectContext*)context {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:kCoreDataEntityNamePhoto
inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"isMostRecent == %#", [NSNumber numberWithBool:YES]];
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"isMostRecent" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
Photo* photo = nil;
if (mutableFetchResults && mutableFetchResults.count > 0) {
photo = [mutableFetchResults objectAtIndex:0];
}
return photo;
}
I have found this to be much faster. Yes, it requires a little more on your part to ensure it is used properly and that you don't ever end up with more than one record marked as isMostRecent but for me this was the best option.
Hope this helps someone else too.
In Swift 4, declare:
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let entity = [Entity]()
func getLastRecord() {
let entityCount = (entity.count - 1)
let lastRecord = entity[entityCount] // This is the las attribute of your core data entity
print(lastRecord)
}

CoreData Basics Help

EDIT:
I have altered the NSPredicate as recommended so that my fetch code look like so. Having printed to the UITextView like this, when I press load it spits out the following:
<NSManagedObject: 0x1c7cf0>(entity: DatedText; id: 0x1420c0 <x - coredata://B52D4F88-0210-4AE2-9DA6-C05ED64FE389/DatedText/p12> ; data: <fault>)
So either its not getting any data data because it hasn't been saved/loaded correctly or I am trying to get the loaded result into a UITextView the incorrect way. Any ideas?
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSEntityDescription *testEntity = [NSEntityDescription entityForName:#"DatedText" inManagedObjectContext:self.managedObjectContext];
[fetch setEntity:testEntity];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"dateSaved == %#", datePicker.date];
[fetch setPredicate:pred];
NSError *fetchError = nil;
NSArray *fetchedObjs = [self.managedObjectContext executeFetchRequest:fetch error:&fetchError];
if (fetchError != nil) {
NSLog(#" fetchError=%#,details=%#",fetchError,fetchError.userInfo);
return nil;
}
NSString *object = [NSString stringWithFormat:#"%#",[fetchedObjs objectAtIndex:0]];
noteTextView.text = object;
I have been having all sorts of problems working out how to use Core Data, so I have gone back to basics, new window based ipad project using core data.
I have added a view and some code which doesn't work, hehe. Im basically trying to save some text to a date, then when going back to that date, the text which was previously saved will be shown again.
There's a tutorial on iPhone developer site here. And there are several sample codes with Core Data as well. These should get you started.
I checked your project and aside from having to synthesize the CoreData properties, I also just noticed you were trying to assign an NSArray to your fetch predicate, but it actually expects an NSPredicate object. You should use this instead:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(dateSaved == %#)", datePicker.date];
[fetch setPredicate:pred];
If you want to set more than 1 predicate you should do that on your predicate string i.e.
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(dateSaved == %#) AND (dateSaved <= %#", datePicker.date, [NSDate date]];
Cheers,
Rog
You most likely crashing because your ivar is managedObjectContext_ but you are using self.managedObjectContext. You also need to synthesize the core data ivars even if you provide a custom getter.
You're setting your NSFetchRequest's predicate to an NSArray, not an NSPredicate.
If you had posted the actual crash, it would probably say something like an unknown selector was sent to an instance of NSArray.

Trouble with Core Data dates

I've got a Core Data model set up, with two entities in a one-to-many relationship (Items, and for each item, there can be multiple ResetDates). I'm pretty confident the model is set up correctly.
I can add new Items, and when doing so, add a new ResetDate (using the current date, with [NSDate date]). I can retrieve and display Items. What I'm having trouble with is retrieving and displaying the ResetDates.
Updated: It works now, thanks very much to the answerers below. Here's the code in question:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"resetDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:&sortDescriptor count:1];
NSMutableArray *sortedResets = [[NSMutableArray alloc] initWithArray:[item.resets allObjects]];
[sortedResets sortUsingDescriptors:sortDescriptors];
NSDate *oldDate = [[sortedResets lastObject] resetDate];
if ( !oldDate ) {
oldDate = [NSDate date];
}
NSInteger numberOfDays = [self timeIntervalWithStartDate:oldDate withEndDate:currentDate]; // This function works fine, when given two NSDate objects
daysSinceLabel.text = [NSString stringWithFormat:#"%d days", numberOfDays];
First, NSArray -objectAtIndex: is not returning nil if you pass it an index that is out of the bounds, it will raise an NSRangeException, when you're not sure about the index, and need to use -objectAtIndex:, you have to call the -count method before to check.
More importantly, an NSArray can't contain a nil value, as nil is not an object.
Then, no, it's not an NSDate object, when you ask item for it's resets relationship (item.resets), you get an NSSet that contain Reset managed objects in return, not NSDate objects, what you want is the resetDate attribute of the returned Reset managed objects, maybe something like this :
// NSArray -lastObject method return nil if the array is empty
// Sending messages to nil is Ok there, so we can call resetDate directly
NSDate *oldDate = [[sortedResets lastObject] resetDate];
if ( !oldDate ) {
oldDate = [NSDate date];
}
Hope that help, and that my English is understandable...
Maybe replacing :
NSDate *oldDate = sortedResets[0];
with :
NSDate *oldDate = [sortedResets objectAtIndex:0];
will help. sortedResets is an NSArray object, not a C array ;)