Here is what I'm trying to accomplish: User touches a button, and and can share something to facebook. The date that the user touched the button is set, and for 24 seconds the user cannot share anything to facebook again. After 24 seconds, the user then is allowed to share another post to facebook.
The error is not facebook related, but it has something to do with the elapsedTime. After the elapsedTime is reset, it doesn't count back up to 24. It just stays at a very small number like "0.0043"
Here's my code:
-(void)postToFacebook
{
if (elapsedTime >= 24) {
[[CCDirector sharedDirector] pause];
[[CCDirector sharedDirector] stopAnimation];
CCAppDelegate *app = (CCAppDelegate*) [[UIApplication sharedApplication] delegate];
SLComposeViewController *faceBookPost = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeFacebook];
[faceBookPost setInitialText:#"test"];
[faceBookPost addURL:[NSURL URLWithString:#"http://www.google.com"]];
[[app navController] presentModalViewController:faceBookPost animated:YES];
faceBookPost.completionHandler = ^(SLComposeViewControllerResult result)
{
[[CCDirector sharedDirector] resume];
[[CCDirector sharedDirector] startAnimation];
[[app navController] dismissModalViewControllerAnimated:YES];
dogeAmount.string = [NSString stringWithFormat:#"%.2LF", _doge];
[self setDate];
NSLog(#"Posted to Facebook");
};
} else if (elapsedTime < 24) {
NSLog(#"%f", elapsedTime);
}
}
And here is the SetDate that is called in the above:
-(void)setDate {
NSString *dateString = [NSDateFormatter localizedStringFromDate:[NSDate date]
dateStyle:NSDateFormatterShortStyle
timeStyle:NSDateFormatterFullStyle];
defaults = [NSUserDefaults standardUserDefaults];
startTime = [NSDate date];
calendar = [NSCalendar currentCalendar];
components = [calendar components:(NSHourCalendarUnit | NSMinuteCalendarUnit) fromDate:startTime];
hour = [components hour];
minute = [components minute];
NSNumber *hourNumber = [NSNumber numberWithInteger:hour];
NSNumber *minuteNumber = [NSNumber numberWithInteger:minute];
[defaults setObject:startTime forKey:#"startTime"];
[defaults setObject:hourNumber forKey:#"hour"];
[defaults setObject:minuteNumber forKey:#"minute"];
[defaults setInteger:elapsedTime forKey:#"elapsedTime"];
NSLog(#"%#",dateString);
elapsedTime = fabs([startTime timeIntervalSinceNow]);
[defaults synchronize];
}
I can't figure out what I'm doing wrong here.
.h
#property (strong, nonatomic) NSDate *dateString;
.m
- (void)share
{
double dateSinceNow = -[self.dateString timeIntervalSinceNow];
if (dateSinceNow > 24)
{
//share to facebook
}
self.dateString = [NSDate date];
}
You are constantly comparing [NSDate date] to now, which will only be the time between the instantiation and now. That is the reason it is always small.
Also, if you want to disable a button, why not disable it and schedule a method to enable it after 24 second delay?
Your fundamental problem is that you calculate elapsedTime in your setDate method, but you only call setDate once, immediately after you post Facebook, so elapsedTime is never going to change after that initial setting.
I presume that you are also storing the post date in NSUserDefaults so that you can keep track of the value across application launches? If so, where do you read the value back in? In viewDidLoad ?
I would get rid of the elapsedTime variable and create a postDate #property -
#property (strong,nonatomic) NSDate *postDate;
Then, in your viewDidLoad method -
self.postDate=[[NSUserdefaults standardUserDefaults] objectForKey:#"postDateKey"];
Now your postToFaceBook can be -
-(void)postToFacebook
{
if (self.postDate == nil || [self.postDate timeIntervalSinceNow] > 24) {
[[CCDirector sharedDirector] pause];
[[CCDirector sharedDirector] stopAnimation];
CCAppDelegate *app = (CCAppDelegate*) [[UIApplication sharedApplication] delegate];
SLComposeViewController *faceBookPost = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeFacebook];
[faceBookPost setInitialText:#"test"];
[faceBookPost addURL:[NSURL URLWithString:#"http://www.google.com"]];
[[app navController] presentModalViewController:faceBookPost animated:YES];
faceBookPost.completionHandler = ^(SLComposeViewControllerResult result)
{
[[CCDirector sharedDirector] resume];
[[CCDirector sharedDirector] startAnimation];
[[app navController] dismissModalViewControllerAnimated:YES];
dogeAmount.string = [NSString stringWithFormat:#"%.2LF", _doge];
self.postDate=[NSDate date];
[[NSUserDefaults standardDefaults] setObject:self.postDate forKey:#"postDateKey"];
NSLog(#"Posted to Facebook");
};
}
}
The simple way to do this is to get a free timer from the performSelector:afterDelay: method on NSObject,,,
- (void)disablePosting {
// set a property that disallows posting, like a bool, or even better
// do something to the ui that disables, like disabling the button
self.postingEnabled = NO;
}
- (void)enablePosting {
self.postingEnabled = YES;
}
Then, to disable for 24 seconds, do this:
[self disablePosting];
[self performSelector:#selector(enablePosting) afterDelay:24];
And your FB code looks like this:
if (self.postingEnabled) {
// do the fb request here, without any references to timeout other than
}
Now you can take all of the other date and elapsedTime related code out.
One little gotcha, remember to cancel the perform request on dealloc
- (void)dealloc {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
Just save the date:
-(void)setStartTime {
defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSDate date] forKey:#"startTime"];
}
Later retrieve the date:
- (BOOL)isTimeExpired {
defaults = [NSUserDefaults standardUserDefaults];
NSDate *startedDate = [defaults objectForKey:#"startTime"];
NSTimeInterval elapsedTime = -[startedDate timeIntervalSinceNow];
return elapsedTime > 24;
}
Note: NSDate simply is the number of seconds since the first instant of 1 January 2001, GMT.
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];
}
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.
I made an UISwitch, and when it is switched on, my app sends 10 notifications with a "repeat interval". It works all fine. But the problem is, when I want to save (remember) the state of my UISwitch and load it, the UILocalNotification aren't being sent anymore.
This is my code:
For the save of the UISwitch state and notification launch:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
BOOL test= [[NSUserDefaults standardUserDefaults] boolForKey:#"switch"];
NSLog(#"%#",test?#"YES":#"NO");
[self.alarmSwitch setOn:test animated:YES];
}
- (void) switchIsChanged:(UISwitch *)paramSender{
if ([paramSender isOn]){
[[NSUserDefaults standardUserDefaults] setBool:self.alarmSwitch.on forKey:#"switch"];
[[NSUserDefaults standardUserDefaults] synchronize];
notification1 = [[UILocalNotification alloc]init];
notification1.fireDate = [NSDate dateWithTimeIntervalSinceNow:5.0];
[notification1 setAlertBody:#"U moet uw voeten controleren!"];
[[UIApplication sharedApplication] scheduleLocalNotification:notification1];
notification1.fireDate = [NSDate dateWithTimeIntervalSinceNow:10.0];
[[UIApplication sharedApplication] scheduleLocalNotification:notification1];
}
}
How can I save the state of the UISwitch in another way in order to get this work? Or is there another solution to fix this?
Your switchIsChanged: method isn't going to be firing when you set the on state programatically. I would recommend moving the code that handles the notifications out of the switchIsChanged: method. Example:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
BOOL test= [[NSUserDefaults standardUserDefaults] boolForKey:#"switch"];
NSLog(#"%#",test?#"YES":#"NO");
[self.alarmSwitch setOn:test animated:YES];
// initially detect if we should fire the bnotifications
if ([self.alarmSwitch isOn]) {
[self fireNotifications];
}
}
-(void)switchIsChanged:(UISwitch *)paramSender{
[[NSUserDefaults standardUserDefaults] setBool:paramSender.on forKey:#"switch"];
[[NSUserDefaults standardUserDefaults] synchronize];
if ([paramSender isOn]){
[self fireNotifications];
}
}
-(void)fireNotifications {
notification1 = [[UILocalNotification alloc]init];
notification1.fireDate = [NSDate dateWithTimeIntervalSinceNow:5.0];
[notification1 setAlertBody:#"U moet uw voeten controleren!"];
[[UIApplication sharedApplication] scheduleLocalNotification:notification1];
notification1.fireDate = [NSDate dateWithTimeIntervalSinceNow:10.0];
[[UIApplication sharedApplication] scheduleLocalNotification:notification1];
}
Note: you may have to make sure that the notifications aren't firing more than once which may cause bugs since viewWillAppear: can be called multiple times.
I have at timeout feature where if the app is idle (in background) for a period of time, I timeout my app and send the user to the login screen. I set the "timedOut" key in user defaults to YES in my application delegate, then reference that key in each view controller, where if it is YES, I segue to the login screen. On the login screen I have a label that displays "Session has timed out" if the "timedOut" is YES. My issue is that if I login, then logout very quickly, the label is displayed, even though I explicitly set that key to NO right after I show the label and then synchronize the user defaults. If I wait a second or two and logout, the label is hidden like it should be. I have solved the "problem", but would like to understand the behavior.
Code from view did load in my login view controller. You would think this changes the isTimedOut to NO, but when I do a quick logout viewdidload is called again, but isTimedOut is YES.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
sessionLabel.hidden = YES;
isTimedOut = [defaults boolForKey:#"isTimedOut"];
if (isTimedOut == YES)
{
sessionLabel.hidden = NO;
defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:NO forKey:#"isTimedOut"];
isTimedOut = NO;
NSLog(#"Timed Out has been reset to %s",[defaults boolForKey:#"isTimedOut"] ? "YES" : "NO");
[defaults synchronize];
}
UPDATE
I replaced the code above using a property in my app delegate instead of NSUserDefaults and the "strange" behavior went away.
eONavAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
isTimedOut = appDelegate.isTimedOut;
sessionLabel.hidden = YES;
//isTimedOut = [defaults boolForKey:#"isTimedOut"];
NSLog(#"Timed Out has been reset to %s",appDelegate.isTimedOut ? "YES" : "NO");
if (isTimedOut == YES)
{
appDelegate.isTimedOut = NO;
sessionLabel.hidden = NO;
}
MORE CODE
To logout, I have UIButtonBarItem calling a segue programmatically. The doLogout property tells the login view controller to run a logout API.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"logoutSegue"])
{
// Get reference to the destination view controller
eoLoginViewController *vc = [segue destinationViewController];
vc.doLogout = YES;
}
}
isTimedOut is set in one location in the app delegate.
-(void)timeoutWithDate:(NSDate *)currentDate
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDate *previousDate = [defaults objectForKey:#"enteredBackground"];
NSTimeInterval distanceBetweenDates = [currentDate timeIntervalSinceDate:previousDate];//TimeInterval is in seconds
NSLog(#"Time between dates in seconds %f",distanceBetweenDates);
double minutesInAnHour = 60;
double minutesBetweenDates = distanceBetweenDates / minutesInAnHour;
NSLog(#"minutesBetweenDates %f",minutesBetweenDates);
if(minutesBetweenDates > 60)
{
isTimedOut = YES;
}
else
{
isTimedOut = NO;
}
}
Use this:
To save a Bool:
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:#"isTimedOut"];
To Load a Bool:
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"isTimedOut"] == YES) {
//it equals yes
}
else {
//it equals no
}
why don't you try something like this? I didn't see your full project but it should work like a charm.
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
then...
- (void)applicationWillResignActive:(UIApplication *)application
{
[[NSUserDefaults standardUserDefaults] setValue:[NSDate date] forKey:#"sleepTime"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
and...
- (void)applicationWillEnterForeground:(UIApplication *)application
{
NSInteger _timeoutInSeconds = 300;
NSDate *_sleepTime = [[NSUserDefaults standardUserDefaults] valueForKey:#"sleepTime"];
if (_sleepTime) {
NSCalendar *_calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *_dateComponents = [_calendar components:NSSecondCalendarUnit fromDate:_sleepTime toDate:[NSDate date] options:0];
if (_dateComponents.second > _timeoutInSeconds) {
// expired session
} else {
// valid session
}
}
}
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!