I just ran into an issue that took me a while to solve and hadn't seen it mentioned on SO, so here it is. I was just trying to programmatically add events to my calendar that are over 1 month old and on iOS7 (not iOS6) these events would not show up in a calendar, and neither were they available when I did a (programatic) query of the calendar. Adding events with future dates or dates up to 1 month in the past worked fine.
Here is the code I am using to add the event:
EKEvent *newCalendarEvent = [EKEvent eventWithEventStore:eventStore];
// In seconds; one hour default duration.
#define DURATION_OF_EVENT 60*60
newCalendarEvent.startDate = self.date;
// If I just use the startDate as the end date, then the height of the event in the calendar is really short.
NSDate *endEventDate = [[NSDate alloc] initWithTimeInterval:DURATION_OF_EVENT sinceDate:self.date];
newCalendarEvent.endDate = endEventDate;
newCalendarEvent.title = [self getEventTitle];
newCalendarEvent.calendar = cal;
NSError *error = nil;
[eventStore saveEvent:newCalendarEvent span:EKSpanThisEvent error:&error];
if (error) {
NSLog(#"CalendarIntegration.integrateDate: Error saving event: %#", error);
}
It turns out this seems to be a side effect of iOS7's Setting app setting under Mail, Contacts, Calendars > Synch. My setting was 1 month. By changing it to 3 months, I was able to create events up to 3 months old and have them displayed in the calendar. Note that I am talking about the calendar on the device that created the calendar event, in which case, I can't see how calendar synch would apply. But apparently, not all (e.g., Apple) would agree with this.
Related
I am creating a game in Spritekit, and I am trying to set up my game in a way that when a player loses all of their lives they have to wait 30 minutes for one of their lives to be restored so that they can play again. I tried using NSTimer to do this but I figured UINotification will be more effective since I want this timer to run whether or not the app is terminated, in the background, being used or not being used. I'm having problems setting this up though.
I have the following code written thus far, when the user reaches the GameOverScene
-(instancetype)initWithSize:(CGSize)size {
if (GameLives < 5 ) {
alarm = [[UILocalNotification alloc] init];
alarm.fireDate = [NSDate dateWithTimeIntervalSinceNow:thirtyNewMinutes];
alarm.timeZone = [NSTimeZone localTimeZone];
[[UIApplication sharedApplication] scheduleLocalNotification:alarm];
alarm.repeatInterval = NSCalendarUnitHour;
NSLog(#"AlarmFireDate = %#", alarm.fireDate);
}
}
The alarm.firedate shows up correctly in the NSLog when I reach the GameOverScene but when I close down my app and restart it, it shows up as null in my view controllers and never fires. How do I get my app to automatically update the user's lives in the background once the notification is scheduled regardless of whether the user is using the app or not? Should it be run in my app delegate?
Should some type of NSDate comparison like the one below run somewhere?
if ([[NSDate date] compare:allarm.fireDate] == NSOrderedDescending) {
GameLives = GameLives + 1;
NSLog(#"SUCCESS");
NSLog(#"COUNT = %lu", (long)GameLives);
}
else if ([[NSDate date] compare:allarm.fireDate] == NSOrderedAscending){
GameLives = GameLives + 1;
NSLog(#"FAILURE");
NSLog(#"COUNT = %lu", (long)GameLives);
}
else if ([[NSDate date] compare:allarm.fireDate] == NSOrderedSame){
NSLog(#"SAME");
NSLog(#"COUNT = %lu", (long)GameLives);
}
I'd be most grateful to anybody that can offer help.
EDIT: RESPONSE TO THE ANSWERS BELOW
I wrote the following code for the NSTimer and the timer starts when the game reaches the GameOver Scene.
-(void)restoreLives{
thirtyNewMinutes = 60 * 30;
update = [NSDate dateWithTimeIntervalSinceNow:thirtyNewMinutes];
if ([[NSDate date] compare:update] == NSOrderedDescending) {
NSLog(#"date1 is later than date2");
NSLog(#"SUCCESS");
NSLog(#"CurrentDate: %#", [NSDate date]);
// LifeText = #"Restored";
GameLives = GameLives + 1;
NSLog(#"LIVES = %ld", (long)GameLives);
// NSLog(#"Level 2 HighScore, %d", _Level1HighScoreNumber);
} else if ([[NSDate date] compare:update] == NSOrderedAscending) {
NSLog(#"date1 is earlier than date2");
NSLog(#"FAILURE");
NSLog(#"CurrentDate: %#", [NSDate date]);
NSLog(#"LIVES = %ld", (long)GameLives);
// Lives = 5;
// NSLog(#"dates are the same");
}
if (GameLives < 4){
[LifeTimer invalidate];
}
And then I created an NSTimer to run the method.
-(void)CheckTime{
LifeTimer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(restoreLives) userInfo:nil repeats:YES];
}
How would I get it to save the target time that you're speaking of?
And, hopefully I'm not overthinking this but from another perspective if I wanted to compare the current NSDate with the [NSDate dateWithTimeIntervalSinceNow:thirtyNewMinutes]; wouldn't I need to save the original date of [NSDate dateWithTimeIntervalSinceNow:thirtyNewMinutes]; when it was originally called so that if the app terminates and the timer runs the code again it compares it to the original time the code was called and doesn't reset the NSDate and compare it to 30 minutes from the time the user restarts the app and the timer begins again.
i.e. 7:15
NSDate comparison to update = [NSDate dateWithTimeIntervalSinceNow:thirtyMinutes];
is called. And timer is set to update lives at 7:45.
7:30 User terminates their app and restarts it at 7:35
When the NSTimer runs again won't it reset the time to be 30 minutes from 7:35 since it's 30 minutes from now? If this is the case how would I go about saving the original date? Please let me know, keep in mind I'm still a beginner with Objective C
A local notification works well if you want to inform the user of something, and can have a payload which you could use to keep track of the information you need, but it's probably not the best solution for you to do your timing work. If the user disables notifications for your app, it would break your functionality.
Instead, when it comes to keeping track of events based on a time, it's best to rely on date comparisons along with timers.
While your app is open, you should use an NSTimer to trigger what you need to do, which I think you have covered.
When you app goes to the background or terminates you should save the target time in some kind of persistent storage (NSUserDefaults, for example). When you app is relaunched or returns from the background, you should compare against that date and either start up the timer or trigger your code that the timer would fire yourself.
Try this to save/restore the date:
// Save the date with the key "NextFireDate"
[[NSUserDefaults standardUserDefaults] setObject:nextFireDate forKey:#"NextFireDate"];
// This forces the values to be saved by the system, which normally only happens periodically
[NSUserDefaults standardUserDefaults] synchronize];
...
// Retrieve the date with the key "NextFireDate"
NSDate *nextFireDate = [[NSUserDefaults standardUserDefaults] objectForKey:#"NextFireDate"];
You'd call the first whenever you go to the background/terminate (also invalidate your current timer) and the second when you finish launching or return from the background (and start a new timer with the retrieved date). NSUserDefaults is basically just a dictionary (that can accept scalars without having to box them yourself) that persists as long as your app is installed.
I'm having a problem getting the date from UIDatePicker. I know the code of getting the date, but it just keeps on getting the CURRENT date, not the date form the picker.
- (IBAction)buttonPressed:(UIButton *)sender {
NSDateFormatter *formate = [[NSDateFormatter alloc]init];
NSDate *settedDate = self.myPickedDate.date;
[formate setDateFormat: #"dd.MMM.yyyy # HH:mm:ss"];
NSString *dateString = [formate stringFromDate:settedDate];
NSLog(#"datestring: %#", dateString);
}
I set myPicker to 15th May 2015 15:30 and Xcode logs out the current date (if it's 16:19 he will log out 22nd.Apr.2015 16:19, no matter what.
Xcode 5.1.1 on simulator iOS 7.1.2 (haven't tried on real device).
The problem is that in your viewDidLoad you are saying this:
self.myPickedDate = [[UIDatePicker alloc] init];
So, think about that code. What you are doing there is creating a new date picker, completely different from the one in your interface, and substituting it for the one in your interface, to which self.myPickedDate was previously set (because it is an outlet). So from now on, self.myPickedDate refers to a different date picker, one that is not in your interface (it is merely held in memory)! Therefore, nothing you do in the interface, such as setting the date in the date picker you see there, has any effect on self.myPickedDate.
Therefore, to solve the problem, delete that line of code.
I'm developing a calendar app which saves and displays various shift rotations. I've been banging by head against the wall the last few days after recent unexplained errors that strangely coincided with an X-Code update to ver. 4.5.2. I couldn't find any similar issues anywhere on Google or Stackoveflow.
The shift rotations are set and saved by the user. The process of creating a shift rotation involves presenting a user with a range of dates, and the user associating each date with a particular shift.
I'm using Core Data with a sqlite store to manage the saved data. The object graph for the above elements involve several "Shift" NSManagedObject entities which contain the details of a shift, such as title, start and end times, and most importantly a date which allows the shift rotation to be displayed later. The "Shift" entities are kept together by another NSManagedObject called "ShiftRotation" which has a many-to-one relationship to the "Shifts".
My app was working properly until recently, when I made few changes to how I was using these "Shift" objects.
My problem is this. The user associates a "Shift" to a date. The date is stored inside a "date" property within the "Shift" NSManagedObject. The context is saved. When I access the "Shift" later, without making any changes, the date that was assigned to the "Shift" appears changed.
I went through my code line by line and added numerous NSLogs to see where or why it's changing, with no results. I noticed that there is no change to the date immediately before AND after I save the context within the class where the save is occurring. There's also no change when I access it from the main Calendar screen. However when I later retrieve the "Shifts" from another screen where I manage all shift rotations, they appear to have changed.
The date change is approximately 31 to 32 days in the future. I can't help but wonder if this is a bug in Core Data or I'm a missing something.
Any explanation or reason why accessing a date from a sqlite persistent store causes a significant and unpredictable date change?
Update: I tried to further narrow down what was happening and where it was happening by using NSNotificationCenter.
I'm using iOS 6.
I registered the root view controller to observe any changes to the managed object context. Particularly any changes to the Objects (i.e. NSManagedObjectContextObjectDidChange).
After I updated the properties of the "Shift" NSManagedObject I receive a notification indicating that the "Shift" object was updated. Here's some code and logs.
This is where the "Shift" entity is created and given a date:
- (NSMutableArray *)arrayWithDateStartingAtDate:(NSDate *)anyDate forNumberOfDays:(NSUInteger)days;
{
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSMutableArray *monthArray = [[NSMutableArray alloc] init];
NSDateComponents *comps = [[NSDateComponents alloc] init];
NSDate *tempDate;
NSManagedObjectContext *moc = [[CSEventStore sharedStore] managedObjectContext];
for (NSUInteger i = 0; i < days; i++) {
[comps setDay:i];
tempDate = [gregorian dateByAddingComponents:comps toDate:anyDate options:0];
Shift *shiftRDO = [Shift initShiftEntityRDOWithDate:tempDate InContext:moc];
[shiftRDO setShiftRotation:_shiftRotation];
NSMutableDictionary *dateDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:tempDate, #"date", shiftRDO, #"shift", nil];
[monthArray addObject:dateDict];
}
return monthArray;
}
This is where the call is made to update it:
[shiftEntity updateWithShiftType:_selectedShiftType inContext:context];
Which calls the method:
- (void)updateWithShiftType:(ShiftType *)shiftType inContext:(NSManagedObjectContext *)context
{
self.title = [NSString stringWithFormat:#"%#",shiftType.title];
self.symbol = [NSString stringWithFormat:#"%#",shiftType.symbol];
self.startTime = [shiftType.startTime dateByAddingTimeInterval:0];
self.endTime = [shiftType.endTime dateByAddingTimeInterval:0];
self.splitStartTime = [shiftType.splitStartTime dateByAddingTimeInterval:0];
self.splitEndTime = [shiftType.splitEndTime dateByAddingTimeInterval:0];
self.isWorkday = [shiftType.isWorkday copy];
self.isTimeOff = [shiftType.isTimeOff copy];
self.typeID = [shiftType typeID];
}
After the above code executes, the first notification is posted in the log:
<Shift: 0x8140b70> (entity: Shift; id: 0x81409e0 <x-coredata:///Shift/tE59A199A-444E-4079-BD8C-0D3E734607783> ; data: {
date = "2012-10-29 04:00:00 +0000";
endTime = "2012-11-02 19:35:38 +0000";
isTimeOff = 0;
isWorkday = 1;
shiftRotation = "0x81406a0 <x-coredata:///ShiftRotation/tE59A199A-444E-4079-BD8C-0D3E734607782>";
splitEndTime = nil;
splitStartTime = nil;
startTime = "2012-11-02 09:35:34 +0000";
symbol = none;
title = Days;
typeID = "991ACC8C-ECE0-4555-9002-7AC233F26CBF";
The "date" property does have the proper assigned date.
Now as soon as I save the context with the following method:
- (BOOL)saveContext
{
NSError *error = nil;
if (__managedObjectContext) {
if ([__managedObjectContext hasChanges] && ![__managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
// More details on error
NSArray* detailedErrors = [error userInfo][NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
}
else {
NSLog(#" %#", [error userInfo]);
}
return NO;
abort();
}
}
return YES;
}
I receive a second notification of a change with the following log:
<Shift: 0x8140b70> (entity: Shift; id: 0x82c9dd0 <x-coredata://7CE834F9-5056-457A-BB2C-B8BA638086F1/Shift/p23> ; data: {
date = "2012-12-02 05:00:00 +0000";
endTime = "2012-11-02 19:35:38 +0000";
isTimeOff = 0;
isWorkday = 1;
shiftRotation = "0x8260dc0 <x-coredata://7CE834F9-5056-457A-BB2C-B8BA638086F1/ShiftRotation/p11>";
splitEndTime = nil;
splitStartTime = nil;
startTime = "2012-11-02 09:35:34 +0000";
symbol = none;
title = Days;
typeID = "991ACC8C-ECE0-4555-9002-7AC233F26CBF";
}),
As you notice, the "date" changed to 2012-12-02, just by saving.
Update: I was finally able to track the cause of the unexplained change. It seems that I was inadvertently changing the "date" property after I was saving the context somewhere else in my code. Perhaps that would have not happened if I had followed carmin's convention (I would have given +1 if I had the reputation). The value was not changing in the Persistent store after all.
Perhaps sharing how I tracked down this bug might benefit anyone else that might have similar problems, not necessarily related to Core Data.
I used Key Value Observing, to observe the "date" property. I began observing immediately after creating the "Shift" entity:
[_shiftEntity addObserver:self forKeyPath:#"date" options:NSKeyValueObservingOptionNew context:NULL];
...and stopped observing after a point in my code where the change had already occurred:
[_shiftEntity removeObserver:self forKeyPath:#"date" context:NULL];
I then implemented the KVO method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"Object being observed: %#", [object description]);
NSLog(#"Value changed to: %#", change[NSKeyValueChangeNewKey]);
}
I placed a break point at the above method. When the change occurred, it stopped at this point, and told me what had change. Most importantly, when the code stopped executing, I looked at the call stack, and traced my way back through the path that the code took that caused the change. I was able to find exactly where my code was making the change.
I don't have much knowledge using KVO but this experience showed me its usefulness even for debugging purposes.
Possible naming conflict. I have learned to prefix all my names. For example, I call a core data property "pDate" instead of "date". I call a stack local "zDate", a parameter "aDate", a static "sDate", and an instance variable "iDate". (I do not prefix method names.) This avoids variable naming conflicts and makes code much easier to understand. Giving something a simple name is to look for a name conflict with the OS which should prefix all of its variables with __ (a double underscore), but that does not always happen. This may not be your problem, but it is possible that the OS uses the term "date" for another purpose and it seems to be modifying "date" to the time of save.
I wrote the following snippet to create an event. Setting the alarm works fine in iOS 4, but in iOS 5 it doesn't get set.
Is this a bug or am I missing something?
EKCalendar *cal = [self.eventStore defaultCalendarForNewEvents];
EKEvent *event = [EKEvent eventWithEventStore:self.eventStore];
event.calendar = cal;
// .......
EKAlarm *alarm = [EKAlarm alarmWithRelativeOffset:-3600];
event.alarms = [NSArray arrayWithObject:alarm];
// .......
I had the same error.
The problem seems that startDate shoudln't be the same as endDate... really silly iOS change!
It seems to be related to that's happening in this ticket: EventKit - App freezes when adding an EKEvent with 2 alarms (iOS 5).
If you take a look at the EventKit section in the iOS 5 changes from iOS 4.3 document, it mentions that some items are deprecated for EKEvent. The hierarchy has changed and a new abstract superclass has been added: EKCalendarItem.
Avoid manipulating the alarms array. You need to add the alarm to your event like this:
EKAlarm *reminder = [EKAlarm alarmWithRelativeOffset:-300];
[event addAlarm:reminder];
This will add a reminder 5 minutes before the start time.
I have 2 questions.
If I understand local notifications the repeatinterval allows me to have a notification scheduled once and it repeated on the same interval each week or month or day of week. I am trying to get a repeatinterval to fire once on say a Tuesday and each week it will fire again on the same day i.e. Tuesday. This should go on every week without needing to schedule another notification. Is that correct. Is is not happening. I am either doing something wrong in code or I am testing it wrong.
In the simulator I run the app schedule the notificaiton. The notification comes up which I view. Then I quit the app and set the system date to 1 week in the future same day of week but no notification so can I test this notification this way by changing the computers system clock. I do not want to have to wait a week for each test.
Here is the code
- (void) scheduleNotificationWithItem:(NSDate *)date interval:(int)frequency {
UILocalNotification *localNotif = [[UILocalNotification alloc]init];
if (localNotif == nil) {
return;
}
localNotif.fireDate = [date addTimeInterval:frequency];
localNotif.timeZone = [NSTimeZone defaultTimeZone];
localNotif.repeatCalendar = [NSCalendar currentCalendar];
localNotif.repeatInterval = kCFCalendarUnitWeekday;
localNotif.applicationIconBadgeNumber = 1;
localNotif.alertBody = [NSString stringWithFormat:NSLocalizedString(#"%#.",nil),#"Weekly Reminder"];
localNotif.alertAction = NSLocalizedString(#"View Notification Details", nil);
localNotif.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication]scheduleLocalNotification:localNotif];
[localNotif release];
}
Please help this is driving me crazy.
Thanks,
Dean
1) Setting your repeat interval to 'NSWeekCalendarUnit' should do the trick. It repeats on a weekly basis on the same day and time as the original notification.
2) I haven't tested in the simulator but changing the clock on an actual iPhone does fire future alerts.