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];
}
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.
So I have managed to set up local notification in my app by doing so.
NSDate *notifTime = [[NSDate date] dateByAddingTimeInterval:time];
UIApplication *app = [UIApplication sharedApplication];
UILocalNotification *notif = [[UILocalNotification alloc] init];
if (notif) {
notif.fireDate = notifTime;
notif.timeZone = [NSTimeZone defaultTimeZone];
notif.repeatInterval = 0;
notif.alertTitle = title;
notif.alertBody = text;
if (sound == nil) {
NSLog(#"default sound");
}
else {
NSLog(#"other sound");
notif.soundName = sound;
};
[app scheduleLocalNotification:notif];
}
inside of a method that is called
- (void)createDateNotification:(NSString*) title alertText: (NSString*)text timeToWait: (NSTimeInterval)time soundToPlay: (NSString*)sound;
and when I call that method, The notification runs just like expected. My problem is that in my game I want it to be when the user clickes on the local notification that it will run a method which now just does so
-(void)rewardUser {
NSLog(#"User rewarded")
}
but sometimes it may run a different method and I don't know how do do it. Anything from swift to objective c will be appreciated. Much thanks!
Override - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification*)notification in your AppDelegate and do your thing.
I managed to solve it by doing this. When creating the alert, I set the action to a public string in my class which is called action and as #songchenwen mentioned, I overrided the didReceiveLocalNotification to check if the action is equal to a number of variables and if so run a method for each one.
I called the mothod like this setting a string for the action
[self createReminderNotification:#"Give50Coins" messageTitle:#"t" messageBody:#"gfdhgsh" timeToWait:2 soundToPlay:#"audio.wav"];
And in the method I initialised the notification setting all the details.
NSDate *notifTime = [[NSDate date] dateByAddingTimeInterval:time];
UIApplication *app = [UIApplication sharedApplication];
UILocalNotification *notif = [[UILocalNotification alloc] init];
action = actionAfterNotifiy;
if (notif) {
notif.fireDate = notifTime;
notif.timeZone = [NSTimeZone defaultTimeZone];
notif.repeatInterval = 0;
notif.alertTitle = title;
notif.alertBody = message;
if (sound == nil) {
NSLog(#"default sound");
}
else {
NSLog(#"other sound");
};
}
[app scheduleLocalNotification: notif];
and in the - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification*)notification
I did this,
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
if ([action isEqualToString:#"Give50Coins"]) {
NSLog(#"wheyy");
}
else {
//random stuff here
}
}
My app is about showing student timetable and I want to add a local notification repeatInterval.
I plan to use switch button for this.
I tried this code and it doesn't work right now :
-(IBAction)theSwitch:(id)sender{
if (switcher.on) {
NSCalendar *gregCalendar = [[NSCalendar alloc]initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComponent = [gregCalendar components:NSYearCalendarUnit | NSWeekCalendarUnit fromDate:[NSDate date]];
[dateComponent setWeekday:3]; // For tuesday
[dateComponent setHour:13];
[dateComponent setMinute:41];
NSDate *fireDate = [gregCalendar dateFromComponents:dateComponent];
UILocalNotification *notification = [[UILocalNotification alloc]init];
[notification setAlertBody:#"ALARM!"];
[notification setFireDate:fireDate];
notification.repeatInterval = NSWeekCalendarUnit;
[notification setTimeZone:[NSTimeZone defaultTimeZone]];
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
else {UIApplication *app=[UIApplication sharedApplication];
[app scheduleLocalNotification:nil];
}
}
So I need some more guidance here on this and some suggestions on how to achieve this.
If you not able to get local notification than please check this code below.
Add code in AppDelegate.m file
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Handle launching from a notification
UILocalNotification *localNotification =
[launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotification) {
NSLog(#"Recieved Notification %#", localNotification);
}
return YES;
}
Add method to handle local notification
- (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notification {
// Handle the notificaton when the app is running
NSLog(#"Recieved Notification %#", notification);
}
Is it possible to setup an app to send notifications from it's self at specified times? Perhaps by putting them in a queue? As of now I'm having to go through a push notification server and that seems like over kill when I'm sending messages out every Tuesday morning. Also, what is this called (so I can better research it on the web)
You can schedule notification like this:
- (void)scheduleNotification
{
//Cancel all previous Local Notifications
[[UIApplication sharedApplication] cancelAllLocalNotifications];
//Set new Local Notifications
Class cls = NSClassFromString(#"UILocalNotification");
if (cls != nil)
{
UILocalNotification *notif = [[cls alloc] init];
//3 days
notif.fireDate = [NSDate dateWithTimeInterval:60.0f*60.0f*24.0f*3.0f sinceDate:[NSDate date]];
notif.timeZone = [NSTimeZone defaultTimeZone];
notif.alertBody = #"Come Back Again! Lets blast All Zombie!";
notif.alertAction = #"PLAY";
notif.soundName = UILocalNotificationDefaultSoundName;
notif.applicationIconBadgeNumber = 1;
[[UIApplication sharedApplication] scheduleLocalNotification:notif];
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application registerForRemoteNotificationTypes:
UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeSound];
[self scheduleNotification];
..
}