I have done a lot of research on this question and wrote several alternatives in Objective-C, but nothing seems to work. My question is: is this even possible in iOS?
Specifically, I want the user to be able to set an "hourly chime" at the top of each hour, be notified, and then have the app restart the timer even if the user does not respond. I know that I can't use NSTimer because of Apple's limits on timers running in the background. I have tried UILocalNotification and a "watchdog" timer to restart the hourly timer if the user does not respond to the UILocalNotification. However, my code in the appdelegate.m that causes the hourly timer to be reset does not execute if the app is in the background, until the user responds to the UILocalNotification. And the "watchdog" timer does not trigger while the app is in the background.
The user can also set "chimes" to go off at 15 minutes, 30 minutes, and 45 minutes past the hour.
How do applications like Sciral HabiTimer do this? Code snippets are below.
AppDelegate.m
UILocalNotification *localNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotification) {
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
[controller startTimer];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSInteger interval = [storage integerForKey:#"intervalToWatch"];
[controller setLocalNotificationExpirationTime:interval];
}
return YES;
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
UIApplicationState state = [application applicationState];
if (state == UIApplicationStateActive) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Reminder"
message:notification.alertBody
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
[controller startTimer];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSString *received = #"YES";
[storage setObject:received forKey:#"notificationWasReceived"];
[storage synchronize];
NSInteger intervalToWatch = [storage integerForKey:#"intervalToWatch"];
[controller setLocalNotificationExpirationTime:intervalToWatch];
// if a timer was started, stop it.
NSTimer *timer = controller.expirationTimer;
if(timer){
[timer invalidate];}
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
NSLog(#"application did enter background");
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller startTimer];
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#end
HourlyChimeTableViewController:
-(void)setLocalNotificationExpirationTime:(NSInteger)intervalToWatch{
NSDate *today = [[NSDate alloc] init];
NSLog(#"Today: %#", today);
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components= [calendar components:(NSCalendarUnitMinute) | (NSCalendarUnitSecond) fromDate: today];
NSInteger minute = [components minute];
NSInteger second = [components second];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
switch(intervalToWatch){
case 0:{
NSInteger difference;
//minute =13;
// NSInteger difference = 10;
[offsetComponents setHour:0];
[offsetComponents setMinute:0];
if(minute >= 0 && minute < 15){
difference = 900 - minute*60 - second;//seconds left to quarter past
NSLog(#"Minute: %ld Second: %ld", (long)minute, (long)second);
}else{
if(minute >= 15 && minute < 30){
difference = 1800 - minute*60 - second;// seconds to half past
NSLog(#"Minute: %ld Second: %ld", (long)minute, (long)second);
}else{
if (minute >= 30 && minute < 45){// seconds to quarter to
difference = 2700 - minute*60 - second;
}else{// seconds to the hour
difference = 3600 - minute*60 - second;
NSLog(#"Minute: %ld Second: %ld", (long)minute, (long)second);
}
}
}
// difference = 60;
[offsetComponents setSecond:difference];
NSDate *fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
self.expirationInterval = difference + 5;
UILocalNotification *notification = [self startLocalNotification:fireDate];
self.localNotification = notification;
[self startNotificationTimer:self.expirationInterval];
break;
-(UILocalNotification *)startLocalNotification:(NSDate *)fireDate{
// [[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *notification = [[UILocalNotification alloc]init];
notification.fireDate = fireDate;
NSLog(#"firedate %#", fireDate);
notification.alertBody =#"Timer Expired!";
notification.alertTitle = #"TimeChime Alert";
notification.timeZone = [NSTimeZone defaultTimeZone];
notification.soundName = UILocalNotificationDefaultSoundName;
self.notificationWasReceived = #"NO";
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
[storage setObject:self.notificationWasReceived forKey:#"notificationWasReceived"];
[storage synchronize];
// notification.repeatInterval = NSCalendarUnitMinute;
[[UIApplication sharedApplication]scheduleLocalNotification:notification];
return notification;
}
-(void)startNotificationTimer:(NSInteger)seconds{
NSTimer *notificationTimer = [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:#selector(notificationTimerDidExpire:) userInfo:nil repeats:YES];
self.expirationTimer = notificationTimer;
NSLog(#"started notification timer for %ld", (long)seconds);
}
-(void)notificationTimerDidExpire:(NSTimer *)notification{
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
self.notificationWasReceived = [storage objectForKey:#"notificationWasReceived"];
NSLog(#"notification timer expired and notificationWasReceived =%#", self.notificationWasReceived);
if(self.localNotification)
{
[[UIApplication sharedApplication]cancelLocalNotification:self.localNotification];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSInteger intervalToWatch = [storage integerForKey:#"intervalToWatch"];
// and reschedule the notification.
[self setLocalNotificationExpirationTime:intervalToWatch];
}
}
I have solved the problem by scheduling multiple repeating notifications. Here is the code (also adds custom actions for iOS8). Note that iOS10 has a completely different way of scheduling local notifications):
//
// AppDelegate.m
// Hourly Chime2
//
// Created by Nelson Capes on 9/20/16.
// Copyright © 2016 Nelson Capes. All rights reserved.
//
#import "AppDelegate.h"
#import <AVFoundation/AVFoundation.h>
#import "HourlyChimeTableViewController.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSError *sessionError = nil;
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error:&sessionError];
[[AVAudioSession sharedInstance] setActive: YES error: &activationError];
// ask the user to allow local notifications
if ([UIApplication instancesRespondToSelector:#selector(registerUserNotificationSettings:)]){
[application registerUserNotificationSettings:[UIUserNotificationSettings
settingsForTypes:UIUserNotificationTypeAlert categories:nil]];
}
UILocalNotification *localNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotification) {
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
[controller startTimer];
[[UIApplication sharedApplication]cancelLocalNotification:localNotification];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSInteger intervalToWatch = [storage integerForKey:#"intervalToWatch"];
NSMutableArray *array = [storage objectForKey:#"timerArray"];
// BOOL YES means to schedule a set of local notifications; NO means don't schedule.
[controller setLocalNotificationExpirationTime:intervalToWatch : NO];
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
}
// define a notification action
UIMutableUserNotificationAction *acceptAction = [[UIMutableUserNotificationAction alloc]init];
acceptAction.identifier =#"ACCEPT_IDENTIFIER";
acceptAction.title = #"Continue";
acceptAction.activationMode = UIUserNotificationActivationModeBackground;
acceptAction.destructive = NO;
acceptAction.authenticationRequired = NO;
UIMutableUserNotificationAction *declineAction = [[UIMutableUserNotificationAction alloc]init];
declineAction.identifier =#"DECLINE_IDENTIFIER";
declineAction.title = #"Stop";
declineAction.activationMode = UIUserNotificationActivationModeBackground;
declineAction.destructive = NO;
declineAction.authenticationRequired = NO;
UIMutableUserNotificationCategory *inviteCategory = [[UIMutableUserNotificationCategory alloc]init];
inviteCategory.identifier = #"INVITE_CATEGORY";
[inviteCategory setActions:#[acceptAction, declineAction] forContext:UIUserNotificationActionContextDefault];
NSSet *categories = [NSSet setWithObjects:inviteCategory, nil];
if([UIApplication instancesRespondToSelector:#selector(registerUserNotificationSettings:)]){
UIUserNotificationSettings *settings = [UIUserNotificationSettings
settingsForTypes:UIUserNotificationTypeAlert categories:categories];
[[UIApplication sharedApplication]registerUserNotificationSettings:settings];
}
return YES;
}
// determine whether the user will allow local notifications.
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings;{
if ([[UIApplication sharedApplication] respondsToSelector:#selector(currentUserNotificationSettings)]){ // Check it's iOS 8 and above
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
UIUserNotificationSettings *grantedSettings = [[UIApplication sharedApplication] currentUserNotificationSettings];
if (grantedSettings.types == UIUserNotificationTypeNone) {
NSLog(#"No permission granted");
[storage setBool:NO forKey:#"permission granted"];
}
else if (grantedSettings.types & UIUserNotificationTypeSound & UIUserNotificationTypeAlert ){
NSLog(#"Sound and alert permissions ");
[storage setBool:YES forKey:#"permission granted"];
[storage setBool:YES forKey:#"sound permission granted"];
[storage setBool:YES forKey:#"alert permission granted"];
}
else if (grantedSettings.types & UIUserNotificationTypeAlert){
NSLog(#"Alert Permission Granted");
[storage setBool:YES forKey:#"permission granted"];
[storage setBool:YES forKey:#"alert permission granted"];
[storage setBool:NO forKey:#"sound permission granted"];
}
[storage synchronize];
}
}
-(void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier forLocalNotification:(nonnull UILocalNotification *)notification completionHandler:(nonnull void (^)())completionHandler{
if ([identifier isEqualToString:#"ACCEPT_IDENTIFIER"]){
[self handleAcceptActionWithNotification:notification];
}else{
if ([identifier isEqualToString:#"DECLINE_IDENTIFIER"]){
[self handleDeclineActionWithNotification:notification];
}
}
completionHandler();
}
-(void)handleAcceptActionWithNotification:notification{
NSLog(#"accept action received");
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
[controller startTimer];
[[UIApplication sharedApplication]cancelLocalNotification:notification];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSInteger intervalToWatch = [storage integerForKey:#"intervalToWatch"];
// BOOL YES means to schedule a set of local notifications; NO means don't schedule.
[controller setLocalNotificationExpirationTime:intervalToWatch : NO];
}
-(void)handleDeclineActionWithNotification:notification{
NSLog(#"decline Action received");
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
UIApplicationState state = [application applicationState];
if (state == UIApplicationStateActive) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Reminder"
message:notification.alertBody
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
// [[UIApplication sharedApplication]cancelAllLocalNotifications];
NSLog(#"app delegate: notification received");
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
[controller startTimer];
[[UIApplication sharedApplication]cancelLocalNotification:notification];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSInteger intervalToWatch = [storage integerForKey:#"intervalToWatch"];
// NSMutableArray *array = [storage objectForKey:#"timerArray"];
// BOOL YES means to schedule a set of local notifications; NO means don't schedule.
[controller setLocalNotificationExpirationTime:intervalToWatch : NO];
// Set icon badge number to zero
// application.applicationIconBadgeNumber = 0;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
NSLog(#"application did enter background");
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
// [controller stopTimer];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
NSLog(#"application did enter foreground");
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.a
NSLog(#"app delegate: applicationDidBecomeActive");
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller startTimer];
if ([[UIApplication sharedApplication] respondsToSelector:#selector(currentUserNotificationSettings)]){ // Check it's iOS 8 and above
UIUserNotificationSettings *grantedSettings = [[UIApplication sharedApplication] currentUserNotificationSettings];
if (grantedSettings.types == UIUserNotificationTypeNone) {
NSLog(#"No permission granted");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"You have not granted permission for Alerts"
message:#"We can't let you know when a timer expires"
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
else if (grantedSettings.types & UIUserNotificationTypeSound & UIUserNotificationTypeAlert ){
NSLog(#"Sound and alert permissions ");
}
else if (grantedSettings.types & UIUserNotificationTypeAlert){
NSLog(#"Alert Permission Granted");
}else{
NSLog(#"No permissions ");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"You have not granted permission for Alerts"
message:#"We can't let you know when a timer expires"
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
}
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#end
The following code shows how to schedule the local notification:
-(void)setLocalNotificationExpirationTime:(NSInteger)intervalToWatch :(BOOL)option{
// BOOL YES means to schedule a set of local notifications; NO means don't schedule.
NSDate *today = [[NSDate alloc] init];
NSLog(#"Today: %#", today);
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components= [calendar components:(NSCalendarUnitMinute) | (NSCalendarUnitSecond) fromDate: today];
NSInteger minute = [components minute];
NSInteger second = [components second];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
// intervalToWatch is an NSInteger set by the UI to determine what kind of notification to set.
// 1 = every 15 minutes; 2 = every half hour; 3 = every hour.
// we calculate the seconds left until the next interval, then schedule a local notification with the seconds left.
switch(intervalToWatch){
case 0:{// notification every 15 minutes
NSInteger difference;
[offsetComponents setHour:0];
[offsetComponents setMinute:0];
if(minute >= 0 && minute < 15){
difference = 900 - minute*60 - second;//seconds left to quarter past
// NSLog(#"Minute: %ld Second: %ld", (long)minute, (long)second);
}else{
if(minute >= 15 && minute < 30){
difference = 1800 - minute*60 - second;// seconds to half past
// NSLog(#"Minute: %ld Second: %ld", (long)minute, (long)second);
}else{
if (minute >= 30 && minute < 45){// seconds to quarter to
difference = 2700 - minute*60 - second;
}else{// seconds to the hour
difference = 3600 - minute*60 - second;
// NSLog(#"Minute: %ld Second: %ld", (long)minute, (long)second);
}
}
}
// schedule repeating local notifications every 15 minutes.
if(option == YES){
[offsetComponents setSecond:difference];
NSDate *fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification = [self startLocalNotification:fireDate];
self.localNotification = notification;// at the next 15 minute interval.
[offsetComponents setSecond:difference + 900];
fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification1 = [self startLocalNotification:fireDate];// at next 30 minutes
[self.timerArray addObject:notification1];
[offsetComponents setSecond:difference + 1800];
fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification2 = [self startLocalNotification:fireDate];// at next 45 minutes
[self.timerArray addObject:notification2];
[offsetComponents setSecond:difference + 2700];
fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification3 = [self startLocalNotification:fireDate];// at next hour
[self.timerArray addObject:notification3];
}
}
break;
case 1:{// notification every 30 minutes at half past the hour.
NSInteger difference;
[offsetComponents setHour:0];
[offsetComponents setMinute:0];
if(minute >= 0 && minute < 30){
difference = 1800 - minute*60 - second;// seconds to half past
}else{
difference = 3600 - minute*60 - second;// seconds to the hour
}
if(option == YES){
[offsetComponents setSecond:difference];
NSDate *fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification = [self startLocalNotification:fireDate];
self.localNotification = notification;// at the next 30 minute interval.
[offsetComponents setSecond:difference + 1800];
fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification2 = [self startLocalNotification:fireDate];// at next 45 minutes
[self.timerArray addObject:notification2];
}
break;
}
case 2:{// notification every hour on the hour
minute = 58;
NSInteger difference = 3600 - minute*60 - second;
if(option == YES){
[offsetComponents setSecond:difference];
NSDate *fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification = [self startLocalNotification:fireDate];
self.localNotification = notification;// at the next 60 minute interval.
}
break;
}
}
}
-(UILocalNotification *)startLocalNotification:(NSDate *)fireDate{
// [[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *notification = [[UILocalNotification alloc]init];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
notification.fireDate = fireDate;
NSLog(#"firedate %#", fireDate);
notification.alertBody =#"Timer Expired!";
notification.alertTitle = #"TimeChime Alert";
notification.timeZone = [NSTimeZone defaultTimeZone];
BOOL sound = [storage boolForKey:#"sound permission granted"];
if(sound){
notification.soundName = UILocalNotificationDefaultSoundName;}
notification.repeatInterval = NSCalendarUnitHour;
notification.category = #"INVITE_CATEGORY";
[[UIApplication sharedApplication]scheduleLocalNotification:notification];
return notification;
}
After a lot of testing, my answer to my own question is "it is not possible." Once the app starts waiting for a local notification, it is blocked. There is no way, short of writing some multi-threaded code (which I have never done), to do anything but let the user respond (i.e., no watchdog timer is going to go off while the app is blocked, e.g., when it is in background).
I did find, however, a way to solve my problem, which I have edited this post to reflect.
Related
I am writing an hourly timer app in objective-c. The app will schedule a local notification to fire optionally at quarter past, half past the hour, and on the hour. The code calculates the firedate correctly and the local notification comes in at the right time. The code for rescheduling the next notification is also working whether the app is in the foreground or the background, but not when the phone is locked. I have not found a way to reschedule a notification for the next interval (quarter hour, half hour, or hour) without receiving a local notification. This means that the user has to receive an alert and then tap on the alert to get the didReceiveLocalNotification method in the app delegate to run. So the crux of the problem is getting a local notification to wake the app delegate when the phone is locked. The launchOption objectForKey method is not called if I set a breakpoint there. Is this possible? Note: this is not a question of using a repeated notification. Here is the app delegate code:
//
// AppDelegate.m
// Hourly Chime2
//
// Created by Nelson Capes on 9/20/16.
// Copyright © 2016 Nelson Capes. All rights reserved.
//
#import "AppDelegate.h"
#import <AVFoundation/AVFoundation.h>
#import "HourlyChimeTableViewController.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSError *sessionError = nil;
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error:&sessionError];
[[AVAudioSession sharedInstance] setActive: YES error: &activationError];
if ([UIApplication instancesRespondToSelector:#selector(registerUserNotificationSettings:)]){
[application registerUserNotificationSettings:[UIUserNotificationSettings
settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|
UIUserNotificationTypeSound categories:nil]];
}
UILocalNotification *localNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotification) {
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
[controller startTimer];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSInteger interval = [storage integerForKey:#"intervalToWatch"];
[controller setExpirationTime:interval];
}
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
UIApplicationState state = [application applicationState];
if (state == UIApplicationStateActive) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Reminder"
message:notification.alertBody
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
[controller startTimer];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSInteger interval = [storage integerForKey:#"intervalToWatch"];
[controller setExpirationTime:interval];
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller startTimer];
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#end
And here is the code that gets called when the local notification is received (note interval is set to 14 minutes just for testing - local notification is received one minute later:
-(void)setExpirationTime:(NSInteger)interval{
NSDate *today = [[NSDate alloc] init];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components= [calendar components:(NSCalendarUnitMinute) | (NSCalendarUnitSecond) fromDate: today];
NSInteger minute = [components minute];
NSInteger second = [components second];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
switch(interval){
case 0:{
NSInteger difference;
minute =14;
// NSInteger difference = 10;
[offsetComponents setHour:0];
[offsetComponents setMinute:0];
if(minute >= 0 && minute < 15){
difference = 900 - minute*60 - second;//seconds left to quarter past
}else{
if(minute >= 15 && minute < 30){
difference = 1800 - minute*60 - second;// seconds to half past
}else{
if (minute >= 30 && minute < 45){// seconds to quarter to
difference = 2700 - minute*60 - second;
}else{// seconds to the hour
difference = 3600 - minute*60 - second;
}
}
}
// difference = 60;
[offsetComponents setSecond:difference];
NSDate *fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
[self startLocalNotification:fireDate];
break;
}
case 1:{
NSInteger difference;
// NSInteger difference = 60;
[offsetComponents setHour:0];
[offsetComponents setMinute:0];
if(minute >= 0 && minute < 30){
difference = 1800 - minute*60 - second;// seconds to half past
}else{
difference = 3600 - minute*60 - second;// seconds to the hour
}
[offsetComponents setSecond:difference];
NSDate *fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
[self startLocalNotification:fireDate];
break;
}
case 2:{
NSInteger difference = 3600 - minute*60 - second;
// NSInteger difference = 60;
[offsetComponents setHour:0];
[offsetComponents setMinute:0];
[offsetComponents setSecond:difference];
NSDate *fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
[self startLocalNotification:fireDate];
break;
}
}
}
-(void)startLocalNotification:(NSDate *)fireDate{
// [[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *notification = [[UILocalNotification alloc]init];
notification.fireDate = fireDate;
notification.alertBody =#"Timer Expired!";
notification.alertTitle = #"TimeChime Alert";
notification.timeZone = [NSTimeZone defaultTimeZone];
notification.soundName = UILocalNotificationDefaultSoundName;
// notification.repeatInterval = NSCalendarUnitMinute;
[[UIApplication sharedApplication]scheduleLocalNotification:notification];
}
In my app delegate the method - (void)application:(UIApplication *)application didReceiveLocalNotification: (UILocalNotification *)notification is never called.
This is how I create the notification:
UILocalNotification *notification = [[UILocalNotification alloc] init];
// set UUID, which we will store in userDefaults for later
NSMutableDictionary *myUserInfo = [[NSMutableDictionary alloc] init];
NSString *uuid = [[NSProcessInfo processInfo] globallyUniqueString];
[myUserInfo setValue:uuid forKey:KEY_UUID];
[myUserInfo setValue:#"month" forKey:KEY_UNIT];
[myUserInfo setObject:#YES forKey:KEY_RESCHEDULE];
NSInteger row = [_wurmProphylaxePickerView selectedRowInComponent:0];
switch (row) {
case 0:
[myUserInfo setValue:#2 forKey:KEY_FREQUENCY];
break;
case 1:
[myUserInfo setValue:#4 forKey:KEY_FREQUENCY];
break;
case 2:
[myUserInfo setValue:#6 forKey:KEY_FREQUENCY];
break;
default:
[myUserInfo setValue:#4 forKey:KEY_FREQUENCY];
break;
}
notification.userInfo = myUserInfo;
// calculate date for next notification, depends on the user's selection
NSDate *today = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *myComps = [[NSDateComponents alloc] init];
[myComps setMinute:1];
notification.fireDate = [calendar dateByAddingComponents:myComps toDate:today options:0];
notification.timeZone = [NSTimeZone localTimeZone];
notification.alertBody = #"My alertBody";
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
And this is in my app delegates, but is never called:
- (void)application:(UIApplication *)application didReceiveLocalNotification: (UILocalNotification *)notification
{
NSDictionary *userInfo = notification.userInfo;
BOOL repeat = [[userInfo objectForKey:KEY_RESCHEDULE] boolValue];
if (repeat)
{
NSInteger frequency = (NSInteger)[userInfo objectForKey:KEY_FREQUENCY];
NSString *unit = (NSString *)[userInfo objectForKey:KEY_UNIT];
NSString *uuid = (NSString *)[userInfo objectForKey:KEY_UUID];
// calculate date for next notification
NSDate *today = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *myComps = [[NSDateComponents alloc] init];
if ([unit isEqualToString:#"month"]) {
//[myComps setMonth:frequency];
[myComps setMinute:frequency];
} else {
}
// create new notification
UILocalNotification *newNotification = [[UILocalNotification alloc] init];
newNotification.fireDate = [calendar dateByAddingComponents:myComps toDate:today options:0];
newNotification.timeZone = [NSTimeZone localTimeZone];
newNotification.alertAction = notification.alertAction;
newNotification.alertBody = notification.alertBody;
newNotification.userInfo = notification.userInfo;
// schedule it
[[UIApplication sharedApplication] scheduleLocalNotification:newNotification];
}
}
tested on iOS 8, not sure about iOS 7...
If the app is not active when the notification fires, you would handle this in didFinishLaunchingWithOptions as illustrated by this example from the Handling Local and Remote Notifications section of the Local and Push Notifications Programming Guide:
- (BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UILocalNotification *localNotif =
[launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotif) {
NSString *itemName = [localNotif.userInfo objectForKey:ToDoItemKey];
[viewController displayItem:itemName]; // custom method
app.applicationIconBadgeNumber = localNotif.applicationIconBadgeNumber-1;
}
[window addSubview:viewController.view];
[window makeKeyAndVisible];
return YES;
}
The didReceiveLocalNotification is called if the app was active when the notification fired.
This question already has an answer here:
IOS Cancelling Local Notifications
(1 answer)
Closed 8 years ago.
I am using Local Notifications. I want to delete already scheduled notifications.I don't know where to write the code.Here is my code ..
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
// Get the current date
NSDate *pickerDate = self.selectedDate;
NSLog(#" self.selectedDate %#", self.selectedDate);
// Break the date up into components
NSDateComponents *dateComponents = [calendar components:( NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit )
fromDate:pickerDate];
NSDateComponents *timeComponents = [calendar components:( NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit )
fromDate:pickerDate];
// Set up the fire time
NSDateComponents *dateComps = [[NSDateComponents alloc] init];
[dateComps setDay:[dateComponents day]];
[dateComps setMonth:[dateComponents month]];
[dateComps setYear:[dateComponents year]];
[dateComps setHour:[timeComponents hour]];
// Notification will fire in one minute
[dateComps setMinute:[timeComponents minute]];
[dateComps setSecond:[timeComponents second]];
NSDate *itemDate = [calendar dateFromComponents:dateComps];
NSLog(#"itemDate %#",itemDate);
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
if (localNotif == nil)
return;
localNotif.fireDate = itemDate;
NSLog(#"itemDate %#", localNotif.fireDate);
localNotif.timeZone = [NSTimeZone defaultTimeZone];
// Notification details
localNotif.alertBody = [_titleTextFieldObj text];
// Set the action button
localNotif.alertAction = #"View";
localNotif.soundName = UILocalNotificationDefaultSoundName;
localNotif.applicationIconBadgeNumber =[[UIApplication sharedApplication] applicationIconBadgeNumber] + 1;
NSLog(#" localNotif.applicationIconBadgeNumber ++ %ld", (long)localNotif.applicationIconBadgeNumber );
// Specify custom data for the notification
NSDictionary *infoDict = [NSDictionary dictionaryWithObject:[_titleTextFieldObj text] forKey:#"someKey"];
localNotif.userInfo = infoDict;
NSArray *notificationArray = [[UIApplication sharedApplication] scheduledLocalNotifications];
//UILocalNotification *notif = [notificationArray objectAtIndex:indexPath.row];
NSLog(#"notif %#",notificationArray);
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
Here I'm writing the removing of notification....
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
application.applicationIconBadgeNumber=1;
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;
NSLog(#"userInfoCurrent %#",userInfoCurrent);
NSString *uid=[NSString stringWithFormat:#"%#",[userInfoCurrent valueForKey:#"uid"]];
NSLog(#"uid %#",uid);
if ([uid isEqualToString:[notification.userInfo objectForKey:#"someKey"]])
{
//Cancelling local notification
[app cancelLocalNotification:oneEvent];
break;
}
}
if (notification) {
NSLog(#"notify %#",notification);
NSString *custom=[notification.userInfo objectForKey:#"someKey"];
NSLog(#"custom %#",custom);
NSString *newString = [custom stringByReplacingOccurrencesOfString:#" " withString:#""];
NSLog(#"newString %#",newString);
NSLog(#"custmky%#",notification.description);
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Message" message:newString delegate:self cancelButtonTitle:#"OK" otherButtonTitles: nil];
alert.delegate=self;
[alert show];
}
}
I am new to UILocalNotifications and Objective-c. Can anyone please help me ....
You can cancel a UILocationNotification either by comparing fire dates to already scheduled UILocalNotifications or using its body.
An example of canceling by fire date:
UIApplication *application = [UIApplication sharedApplication];
NSDate *dateToCancel = nil; // Set this to the date you want to cancel
for (UILocalNotification *notification in [application scheduledLocalNotifications])
{
if (notification.fireDate == dateToCancel)
{
[application cancelLocalNotification:notification];
}
}
Now if you have a notification pointer you can just call the cancel local notification without needing to loop through already scheduled notifications. If you want you can also add an Id tag to the notification through key-object methods.
No matter in which class that code will lay, it's anywhere using UIApplication singleton...
You can cancel all notification using:
[[UIApplication sharedApplication] cancelAllLocalNotifications];
If you want to remove a particular notification, you can use userinfo of notification object, when you create a local notification add a unique ID to that. Later you can use that ID for removing local notification.
For that you can use the following code:
NSString *notificationId = #"id_to_cancel";
UILocalNotification *notification = nil;
for(UILocalNotification *notify in [[UIApplication sharedApplication] scheduledLocalNotifications])
{
if([notify.userInfo objectForKey:#"ID"] isEqualToString:notificationId ])
{
notification = notify;
break;
}
}
[[UIApplication sharedApplication] cancelLocalNotification:notification];
I am making a timer app. The user sets the time and the app counts down from there. I have added a UILocalNotification, so that it pops up even when you aren't in the app to tell you the timer has finished:
IN MY VIEW CONTROLLER:
- (void)setupLocalNotifications {
[[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
totalSeconds = (setHour * 60 * 60) + (setMinute * 60) + (setSecond);
NSDate *now = [NSDate date];
NSDate *dateToFire = [now dateByAddingTimeInterval:totalSeconds];
localNotification.fireDate = dateToFire;
localNotification.alertBody = #"Timer Done";
localNotification.soundName = UILocalNotificationDefaultSoundName;
localNotification.applicationIconBadgeNumber = 1; // increment
NSDictionary *infoDict = [NSDictionary dictionaryWithObjectsAndKeys:#"Object 1", #"Key 1", #"Object 2", #"Key 2", nil];
localNotification.userInfo = infoDict;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
}
IN MY APPDELEGATE:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.;
ScrollViewController *sv = [[ScrollViewController alloc] init];
UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (notification) {
[self showAlarm:notification.alertBody];
NSLog(#"AppDelegate didFinishLaunchingWithOptions");
application.applicationIconBadgeNumber = 0;
}
self.window.rootViewController = sv; // Make tab bar controller the root view controller
[self.window makeKeyAndVisible];
return YES;
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
[self showAlarm:notification.alertBody];
application.applicationIconBadgeNumber = 0;
NSLog(#"AppDelegate didReceiveLocalNotification %#", notification.userInfo);
}
- (void)showAlarm:(NSString *)text {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Timer"
message:text
delegate:self
cancelButtonTitle:#"Stop Timer"
otherButtonTitles:nil];
[alertView show];
}
What happens is, I set a UILocalNotification to go off after the user-defined number of seconds has passed. However, my app allows you to pause the timer. When paused, the UILocalNotification will carry on, and go off after the seconds have passed. Is there any way to pause the local notification?
A local notification cannot be paused. If the timer is paused in your application,
you should cancel the notification, and create/schedule a new one if the timer
is resumed.
I have set up a daily reminder local notification for my app. It works great when the app is in the background. However, when the app is closed/not running, when the user taps LAUNCH in the notification, the app launches the main screen, then goes back to the home screen.
I have tried numerous code solutions on here, most involvingsomething along these lines:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (notification) {
// handle your notification here.
}
}
But no matter where I put this, the app launches but now turns to black.
Here is my code, can anyone help me figure out what to do here?
Please not, I am using Dreamweaver/phonegap to build the app.
//
// assignmentAppDelegate.m
// assignment
//
// Created by Noel Chenier on 23 December, 2011.
// Copyright 2011. All rights reserved.
//
#import "assignmentAppDelegate.h"
#import "PhoneGapViewController.h"
#implementation assignmentAppDelegate
- (id) init
{
/** If you need to do any extra app-specific initialization, you can do it here
* -jm
**/
return [super init];
}
/**
* This is main kick off after the app inits, the views and Settings are setup here.
*/
- (void)applicationDidFinishLaunchingWithOptions:(UIApplication *)application
{
{
UILocalNotification *localNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
}
[[UIApplication sharedApplication]
registerForRemoteNotificationTypes:
UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeSound];
NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];
NSDictionary *appDefaults =[NSDictionary dictionaryWithObject:#"NO" forKey:#"enableNotifications"];
[defaults registerDefaults:appDefaults];
[defaults synchronize];
[ super applicationDidFinishLaunching:application ];
}
- (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notif {
}
-(id) getCommandInstance:(NSString*)className
{
/** You can catch your own commands here, if you wanted to extend the gap: protocol, or add your
* own app specific protocol to it. -jm
**/
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"enableNotifications"]) {
[[UIApplication sharedApplication] cancelAllLocalNotifications];
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
NSCalendar *calender = [NSCalendar autoupdatingCurrentCalendar];
NSDate *currentDate = [NSDate date];
NSDateComponents *dateComponents = [calender components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit)
fromDate:currentDate];
NSDateComponents *temp = [[NSDateComponents alloc]init];
[temp setYear:[dateComponents year]];
[temp setMonth:[dateComponents month]];
[temp setDay:[dateComponents day]];
[temp setHour: 9];
[temp setMinute:00];
NSDate *fireTime = [calender dateFromComponents:temp];
[temp release];
// set up the notifier
UILocalNotification *localNotification = [[UILocalNotification alloc]init];
localNotification.fireDate = fireTime;
localNotification.timeZone = [NSTimeZone defaultTimeZone];
localNotification.alertBody = #"Don't Forget Your 365 Day Photo Challenge!";
localNotification.alertAction = #"LAUNCH";
localNotification.soundName = UILocalNotificationDefaultSoundName;
localNotification.repeatInterval = NSMinuteCalendarUnit;
localNotification.applicationIconBadgeNumber = 0;
[[UIApplication sharedApplication]scheduleLocalNotification:localNotification];
[localNotification release];
}
else {[[UIApplication sharedApplication] cancelAllLocalNotifications];}
return [super getCommandInstance:className];
}
/**
Called when the webview finishes loading. This stops the activity view and closes the imageview
*/
- (void)webViewDidFinishLoad:(UIWebView *)theWebView
{
return [ super webViewDidFinishLoad:theWebView ];
}
- (void)webViewDidStartLoad:(UIWebView *)theWebView
{
return [ super webViewDidStartLoad:theWebView ];
}
/**
* Fail Loading With Error
* Error - If the webpage failed to load display an error with the reson.
*/
- (void)webView:(UIWebView *)theWebView didFailLoadWithError:(NSError *)error
{
return [ super webView:theWebView didFailLoadWithError:error ];
}
/**
* Start Loading Request
* This is where most of the magic happens... We take the request(s) and process the response.
* From here we can re direct links and other protocalls to different internal methods.
*/
- (BOOL)webView:(UIWebView *)theWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
return [ super webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType ];
}
- (BOOL) execute:(InvokedUrlCommand*)command
{
return [ super execute:command];
}
- (void)dealloc
{
[ super dealloc ];
}
#end
For local notification,you need to add following things in :
- (void)applicationDidFinishLaunchingWithOptions:(UIApplication *)application
{
UILocalNotification *localNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
}
- (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notif {
}
As it turns out...
I'm not sure why it was crashing, but in the event anyone else having this problem comes across this, the notification will launch the app when your build for relase....it just doesnt appear to work when it's built/run for debug or another state.
So when you run, run for RELEASE and it should work just fine!