I have a ViewController that performs a random shuffle on an array and spits out text to a Label (in the viewDidLoad method). The problem is that whenever I navigate to the same ViewController it performs the shuffle again and I only need it to shuffle ONCE every day.
So I need to check if this ViewController has been loaded before on the SAME day (i.e. since midnight) and I can then put the shuffle into an if statement. Could I schedule the shuffle at midnight regardless of the app being open or not?
I have looked into setting a boolean to NSUserDefaults: something like hasLoadedSinceMidnight but then can't work out how to RESET the boolean at midnight.
You could implement the AppDelegate's significantTimeChange method:
-(void)applicationSignificantTimeChange:(UIApplication *)application {
//tell your view to shuffle
}
This method is called every midnight and during significant time changes such as a time zone change. If your app is closed when the event is received, this method will be called when your app is next opened.
More information can be viewed here
An additional way to do the same thing inside of your ViewController instead of in the AppDelegate would be to add:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(performAction) name:UIApplicationSignificantTimeChangeNotification object:nil];
and then you can just perform your shuffle operation in the -(void)performAction; method of that ViewController.
Instead of storing a BOOL, you could store the date/time (NSDate) of the last shuffle.
Then check whether or not it has passed midnight since the last shuffle by comparing the stored date and the current date in the viewDidAppear.
See NSTime Documentation: http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSDate_Class/Reference/Reference.html
And NSDateFormatter Documentation:
https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSDateFormatter_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40003643
UPDATE:
As requested, here is some sample code. Admittedly, there very well may be better solutions to this, but I believe this code snippet will help you get you past your problem. What this does is check if there is a date saved using NSUserDefaults, then compare with the current date. If the dates do not match, shuffle the array, then save the current date (again using NSUserDefaults). (I've taken the liberty of making the assumption that time will indeed continue to move forward, so it doesn't check to make sure that lastSavedDate is before the currentDate.)
NSDate *currentDate = [[NSDate alloc] init];
NSDate *lastShuffleDate = [[NSUserDefaults standardUserDefaults] objectForKey:#"lastShuffleDate"];
// check to see if there is a prior shuffle date
// if there is not, shuffle the array and save the current date
if (!lastShuffleDate) {
NSLog(#"No object set for 'lastShuffleDate'");
//[self shuffleMyArray];
[[NSUserDefaults standardUserDefaults] setObject:currentDate forKey:#"lastShuffleDate"];
return;
}
// set up the date formatter
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSLocale *usLocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
[dateFormatter setLocale:usLocale];
[dateFormatter setDateStyle:NSDateFormatterShortStyle];
NSLog(#"Current Date: %#", [dateFormatter stringFromDate:currentDate]);
NSLog(#"Saved Date: %#", [dateFormatter stringFromDate:lastShuffleDate]);
// check to see if the dates are the same by comparing the dates as a string
if (![[dateFormatter stringFromDate:currentDate] isEqualToString:[dateFormatter stringFromDate:lastShuffleDate]]) {
NSLog(#"Dates are different...!");
//[self shuffleMyArray];
} else {
NSLog(#"Dates are the same... (midnight has not passed)");
}
// save the time of the last shuffle
[[NSUserDefaults standardUserDefaults] setObject:currentDate forKey:#"lastShuffleDate"];
At this point, there is no real reason for you to check the times, but I've included it in case you were curious.
// remote dateStyle and set timeStyle to check times
[dateFormatter setDateStyle:NSDateFormatterNoStyle];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
NSLog(#"Current Time: %#", [dateFormatter stringFromDate:currentDate]);
NSLog(#"Saved Time: %#", [dateFormatter stringFromDate:lastShuffleDate]);
Related
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
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
I wrote this code to reload the UItableView with an events that has the same date as the current date when the user click on todays events UIButton in the main view controller but the problem is the below code is not reloading the right data (it just gives the initial data without comparing the date of the event with the date of the calendar in the IPhone), my data comes from a json file within the project and consists from NSArray of events, each has a different value for each key and one of these keys is the data of that event ("date"), can anyone plz clarify for me why the below code is not returning the right data ??
#implementation MainViewController {
NSArray *_events;
}
....
- (IBAction)upcomingEvents:(id)sender {
NSDate *currDate = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:#"dd-MM-YYYY"];
NSString *dateString = [dateFormatter stringFromDate:currDate];
for (Events *event in _events){
if([event.date isEqualToString:dateString]){
[self.myTableView reloadData];
}
}
}
If you're using a UITableViewDataSource you should make sure that it returns only the events that match your condition [event.date isEqualToString:dateString]
You can do
NSArray * dateEvents = [_events filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(Events * event, NSDictionary *bindings)
{
return [event.date isEqualToString:dateString];
}];
Then you can use dateEvents for your UITableViewDataSource.
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
I am listing the events in my app. User can create, edit and delete the events. In viewDidLoad method I fetch all events I need and push them into an array. It works like expected.
For creating, editing and deleting events I use EKEventEditViewController and EKEventViewController which works pretty well. In delegate methods of the controllers I make the changes I need on my array and reload my view.
Of course I would like also know and handle, if user make some changes from another app (like built-in calendar app). So I observe EKEventStoreChangedNotification. From that notification I get only "changes have been occurred" and not which event or from which app. Actually what I want to know is, if the change has been occurred from my app or another app and which events have been changed. Since I already handle the changes(from my app) in EKEventEditViewControllerDelegate method, I do not need to handle them again.
If I do not know which objects have been changed, I have to fetch ans sort all of them.
For now I have only 5 events in the calendar(development device), of course it is not a problem to fetch and sort all events, but if user has more then 1000, it is overkill for maybe only one event change.
So my question is: How to handle EKEventStoreChangedNotification?
You can detect exactly which event has been changed by the following code [Disclaimer code is not my idea, I have found it in another Stack Overflow answer and modified it a little bit].
I'm using a lib called "JSCalendarManager" for interaction with eventstore and in my case as the events created using my App and synced with iCalendar I already saved their eventIdentifier in local DB , I can retrieve my time bound to search for events in iCalendar and get match for changed one.
+(void)iCloudStoreChanged:(NSNotification*)eventStoreChangeNotification{
NSArray* allScheduleRecords =[self getAllScheduleRecordSyncedToICalendar];
NSDate* startDate = [NSDate new];
NSDate* endDate = [NSDate new];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
if (allScheduleRecords.count >= 2) {
startDate = [dateFormatter dateFromString:[[allScheduleRecords firstObject] objectForKey:#"meetingTime"]];
endDate = [dateFormatter dateFromString:[[allScheduleRecords lastObject] objectForKey:#"meetingTime"]];
}else if (allScheduleRecords.count > 0){
startDate = [dateFormatter dateFromString:[[allScheduleRecords firstObject] objectForKey:#"meetingTime"]];
NSDate *today = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [gregorian components:(NSEraCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit) fromDate:today];
components.day = 1;
endDate = [gregorian dateFromComponents:components];
}else{
}
NSArray *ekEventStoreChangedObjectIDArray = [eventStoreChangeNotification.userInfo objectForKey:#"EKEventStoreChangedObjectIDsUserInfoKey"];
[calendarManager findEventsBetween:startDate
and:endDate
withSearchHandler:^(BOOL found, NSError *error, NSArray *eventsArray) {
[eventsArray enumerateObjectsUsingBlock:^(EKEvent *ekEvent, NSUInteger idx, BOOL *stop) {
// Check this event against each ekObjectID in notification
[ekEventStoreChangedObjectIDArray enumerateObjectsUsingBlock:^(NSString *ekEventStoreChangedObjectID, NSUInteger idx, BOOL *stop) {
NSObject *ekObjectID = [(NSManagedObject *)ekEvent objectID];
if ([ekEventStoreChangedObjectID isEqual:ekObjectID]) {
// Log the event we found and stop (each event should only exist once in store)
NSLog(#"calendarChanged(): Event Changed: title:%#", ekEvent.title);
[self updateAppointmentForEvent:ekEvent];
*stop = YES;
}
}];
}];
}];}
Instead of fetching all events, can you not update only the events that are onscreen/active.