Comparing two NSDates constantly even outside the app - objective-c

I decided for my first application i would create an alarm application. So far i understand to create two NSDate objects and compare them with isEqualToDate and UILocalNotifications for notifications. How do i continually compare the set date to the current date. Do i create a loop, or is there a more efficient way on Objective C? How do i continually check in the background?
[very new to objective c, sorry and thank you]
This what iv'e started:
NSDate * now = [NSDate date];
NSDate * later = [[NSDate alloc] initWithString:#"2001-03-24 10:45:32 +0600"];
NSComparisonResult result = [later compare:mile];
NSLog(#"%#", later);
NSLog(#"%#", mile);

You don't need to create loops or compare dates yourself. One way to have your alarm sound could be via a local notification. A quick sample code to schedule yourself a local notification:
// Initialize your notification
NSUserNotification *notification = [[NSUserNotification alloc] init];
// Set the title of your notification
[notification setTitle:#"Alarm finished!"];
// Set the text of your notification
[notification setInformativeText:#"My Text"];
// Set the time and date on which the notification will be delivered (in this case 60 seconds after the current time)
[notification setDeliveryDate:[NSDate dateWithTimeInterval:60 sinceDate:[NSDate date]]];
// Set the sound, this can be either nil for no sound, NSUserNotificationDefaultSoundName for the default sound)
[notification setSoundName:NSUserNotificationDefaultSoundName];
// Schedule the notification
[[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification:notification];
If you don't want to use a local notification but want to execute something different once your alarm finishes, you could use NSTimer to execute your action once the alarm fires.

Related

Restoring Game Lives using UILocalNotification

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.

Enable/disable a perticular UILocalNotification and set multiple notification once

NOTE: Need to know how to Set a number of alarm or notification in between two given dates regularly?
I have to create an app where,i have to enable/disable multiple UILocalnotification set in a tableview.'
If i choose a date i have to then set time on that perticular date and also need to set the notification before timePlay (5,10,15,20 min prior).
And End date: the date until when the notification plays regularly.
How to set all the notification for a perticular notification ID at once?
How to disable a perticular notification?
ALSO PLEASE TELL ME : CAN HOW CAN I SET UILOCALNOTIFICATION using database?
I have create a database having
Notification ID //unique id of notification
Notification Name //notification title
Time1 //can set five time the notification will show
Time2(optional)
Time3(optional)
Time4(optional)
Time5(optional)
Before timePlay//can show the notification before time of notification 1 to 5 above
Start Date // the date from which the notification start
End Date //the date when the notification stops.
i can set simple notification like this
// UILocalNotification properties:
// alertBody - the message displayed in the notification
// alertAction - if notification is displayed as an alert, this is the label of the action button, if not specified, "Launch" will be used
// soundName - the name of a sound file (AIFF, CAF or WAV) to be played when the notification appears, if not specified, no sound will be played. To use the default sound UILocalNotificationDefaultSoundName can be provided.
// userInfo - you can pass an NSDictionary object with additional data to be used by our app after the notification fires (optional)
// fireDate - an NSDate object specifying the date and time for the local notification to be fired (obligatory)
// timeZone - an NSTimeZone object. If specified, the fireDate is measured against the user's local time zone, if not against universal time
// repeatCalendar - the calendar (NSCalendar) the system should refer to when it reschedules a repeating notification
// repeatInterval - the calendar interval (NSCalendarUnit) at which to reschedule the notification, the default is 0, which means don't repeat
// alertLaunchImage - will be presented when your app is run or summoned from the background
// Create a new local notification
UILocalNotification *notif = [[UILocalNotification alloc] init];
notif.alertBody = #"Wake up! Its tuition time!";
notif.fireDate = [NSDate dateWithTimeIntervalSinceNow:60]; // 60 seconds
notif.soundName = UILocalNotificationDefaultSoundName;
notif.applicationIconBadgeNumber += 1;
Try scheduling which will enable the notification and use cancel which will be disable the notification:-
-(void)enableNotification
{
[self cancelAlarm]; //clear any previous alarms
UILocalNotification *alarm = [[UILocalNotification alloc] init];
alarm.alertBody = #"alert msg";
alarm.fireDate = [NSDate dateWithTimeInterval:alarmDuration sinceDate:startTime];
alarm.soundName = UILocalNotificationDefaultSoundName;
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:self.name forKey:kTimerNameKey];
alarm.userInfo = userInfo;
[[UIApplication sharedApplication] scheduleLocalNotification:alarm];
}
-(void)disableNotification{
for (UILocalNotification *notification in [[[UIApplication sharedApplication] scheduledLocalNotifications] copy]){
NSDictionary *userInfo = notification.userInfo;
if ([self.name isEqualToString:[userInfo objectForKey:kTimerNameKey]]){
[[UIApplication sharedApplication] cancelLocalNotification:notification];
}
}
at time of notification schedule give a unique notification key in notification's userinfo and to cancel notification you can use below code as where
UIApplication *app = [UIApplication sharedApplication];
NSArray *eventArray = [app scheduledLocalNotifications];
for (int i=0; i<[eventArray count]; i++)
{
UILocalNotification* oneEvent = [eventArray objectAtIndex:i];
NSDictionary *userInfoCurrent = oneEvent.userInfo;
NSString *uid=[NSString stringWithFormat:#"%#",[userInfoCurrent valueForKey:#"uid"]];
if ([uid isEqualToString:uidtodelete])
{
//Cancelling local notification
[app cancelLocalNotification:oneEvent];
break;
}
}

Retrieve users ringtone library

I was wondering if there was a way to retrieve users ringtones or sounds. I am not interested in their music playlist but like 30 second clips of audio that could work for uilocalnotifications. Does anyone know how to do this? Thanks!
See this answer.
how to get the system ringtones programmingly in ios?
If you still want to fire an audio notification, this article should get you started.
It seems you can assign a filename to the soundName property, or pass UILocalNotificationDefaultSoundName for (what I would guess is) the user's selected notification sound.
- (void)scheduleNotificationWithItem:(ToDoItem *)item interval:(int)minutesBefore {
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
localNotif.soundName = UILocalNotificationDefaultSoundName;
...
[localNotif release];
}

how do dispatch queues work

Im a bit confused here, Im using queues and I got to a point where Im a bit lost.
I have a method named getPeople who has to fetch pictures of users from the server. In order not to block the app I used this:
-(IBAction)seeMorePeople{
dispatch_queue_t getPeopleQueue = dispatch_queue_create("Pinta Ocupantes", NULL);
dispatch_async(getPeopleQueue, ^{
[self getPeople];
});
dispatch_release(getPeopleQueue);
}
The previous code is executed everytime the user taps a button. Something like "Give me pics from this album" and then another tap "Now I want people's pictures from that other album", diferent pics and different amount of pictures. If the user taps the buttons quite fast, the first queue wont finish fetching the data when the second one is already starting. With in getPeople I store the data in an NSMutableArray, so when the 2 queues are executing at the same time both are writing on the same Array and the app crashes due to out of bounds exception.
The way getPeople goes through the data is something like this:
-(void)getPeople:(NSDictionary *)peopleDictionary{
//I receive an NSDictionary and I go through it
NSArray *keys = [peopleDictionary allKeys];
int indexOfArray = 0;
for(NSString *key in keys){
//Complex operation that are not important
[peopleInArray insertObjetAtIndex:indexOfArray];//People in array is a global variable
indexOfArray++;
}
}
What I can't figure out is how to get out of this, I thought of stopping the first queue when the second one comes in, but GCD doesnt have this option... any other way to get this done, hopefully without a major recoding, anyway right now Im out of ideas, so any clue will help.
I would suggest that you avoid synchronizing with semaphores, if possible. The design of GCD is to avoid that. A background task should prepare data but not touch outside state. When the data is prepared, it should dispatch the updating of outside state to either a serial queue or, if the state is bound to the GUI, to the main queue.
So, something like this:
-(void)getPeople:(NSDictionary *)peopleDictionary{
//I receive an NSDictionary and I go through it
NSArray *keys = [peopleDictionary allKeys];
for(NSString *key in keys){
//Complex operation that are not important
dispatch_async(dispatch_get_main_queue(), ^{
[peopleInArray addObject:<whatever>];
});
}
}
If you rather want to replace the array, instead of having two threads adding to it in interleaved fashion, you'd accumulate the whole array in the background and dispatch setting the entirety of peopleInArray to the main queue.
If you want cancellation, you can implement it yourself with a flag, or you should maybe consider using NSOperation and NSOperationQueue instead of GCD. Those have a concept of cancellation built in, although your custom operations still need to check if they've been cancelled and stop working.
You are right, there is no way to stop a queue which was dispatched. One thing you could do to make sure that only one queue is accessing getPeople at the same time is using semaphores (this might be even more interesting).
If you just want to avoid that the users clicks the button multiple times you could use a bool variable stillExecuting which is set to YES in your asynchronous dispatch and set to NO at the end of getPeople. Before creating getPeopleQueue you simply check if getPeople is still executing.
if(!stillExecuting) {
dispatch_queue_t getPeopleQueue = dispatch_queue_create("Pinta Ocupantes", NULL);
dispatch_async(getPeopleQueue, ^{
[self getPeople];
});
dispatch_release(getPeopleQueue);
}
NSString *lastLoginTime =#" Your last login time";
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate* date1 = [dateFormat lastLoginTime];
NSDate* date2 = [dateFormat dateFromString:[NSString currentDateTime]];
NSTimeInterval secondsSinceLastLogin = [date2 timeIntervalSinceDate:date1];
// NSLog(#"Seconds Since Last Login: %g", secondsSinceLastLogin);
int hours = (int)secondsSinceLastLogin / 3600;

UILocalNotification repeatInterval

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.