NSTimeInterval not always firing - ios

I'm working on a simple radio alarm clock app for a non-mainstream stream I'd like to wake up to. It uses the opting out of background trick to stay running when the phone is locked, similar to Rise/Sleepcycle.
Originally the alarm wouldn't fire the next day if I was to set the alarm before midnight but would always fire pretty much on the turn of the minute if set ahead of the current time but before 00:00
So I've made some adjustments so that the date is also checked. I also included conditions so that it's impossible to set a time before the current time. Now while this takes care of the next day issue, I'm finding the fire itself is temperamental. Sometimes it works perfectly, sometimes there is a huge delay (up to 10 minutes in cases) sometimes it never fires at all. I really cannot figure out for the life of me why this happens.
Below is my code. Does anyone have any ideas as to why it's not consistent?
- (void)viewDidLoad {
[super viewDidLoad];
radio = [[Radio alloc] initWithUserAgent:#"my app"];
[radio connect:STREAM_URL withDelegate:self withGain:(1.0)];
// dateTimePicker.date = [NSDate date];
dateTimePicker.date =
[[ NSDate alloc ] initWithTimeIntervalSinceNow: (NSTimeInterval) 2];
dateTimePicker.minimumDate =
[[ NSDate alloc ] initWithTimeIntervalSinceNow: (NSTimeInterval) 0 ];
}
-(void) presentMessage:(NSString *)message {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Radio Alarm" message: message delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
}
- (IBAction)alarmSetButtonTapped:(id)sender {
NSLog( #"Alarm Set Button Tapped");
NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.timeZone = [NSTimeZone defaultTimeZone];
dateFormatter.timeStyle = NSDateFormatterShortStyle;
dateFormatter.dateStyle = NSDateFormatterShortStyle;
NSString * dateTimeString = [dateFormatter stringFromDate: dateTimePicker.date];
NSLog( #"Alarm Set Button Tapped : %#", dateTimeString);
[self presentMessage:[NSString stringWithFormat:#"Alarm set for %#", dateTimeString]];
NSDate *alarmFireDate = dateTimePicker.date;
NSTimeInterval differenceFromNow = [alarmFireDate timeIntervalSinceNow];
//[alarmFireDate performSelector:#selector(PlayAlarm:) withObject:nil afterDelay:differenceFromNow];
[self performSelector:#selector(PlayAlarm:)
withObject:(self)
afterDelay:(differenceFromNow)];

you can use
[NSTimer scheduledTimerWithTimeInterval:differenceFromNow
target:self
selector:#selector(PlayAlarm:)
userInfo:nil
repeats:NO];
This timer is quite accurate.
If you need to abort the timer, just save the returned NSTimer* from this in a property and call
[myTimer invalidate];

Related

Timer in Today Extension Freezes Extension

I have a timer to a date counting down in my today widget. I am gathering the date from my View Controller and then starting a timer in the extension and from there I am displaying the dates in three labels, Hours, Minutes, and Seconds. In my app... this is perfect, but not so true in the notification center. The countdown loads correctly and works great for about 20 seconds. After that it skips a second and freezes, then it starts again in about 4 seconds and then it will reset the whole widget (everything disappears) and then about 2 seconds later the whole widget resets. Is this a memory warning or something?
View Controller:
mainTextLabel.text = 436496400
NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.nicmac.nextgame"];
[sharedDefaults setObject:mainTextLabel.text forKey:#"MyNumberKey"];
[sharedDefaults synchronize];
Today View Controller
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(userDefaultsDidChange:)
name:NSUserDefaultsDidChangeNotification
object:nil];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.preferredContentSize = CGSizeMake(320, 300);
[self performSelector:#selector(updateNumberLabelText) withObject:nil afterDelay:1.0];
}
- (void)userDefaultsDidChange:(NSNotification *)notification {
[self performSelector:#selector(updateNumberLabelText) withObject:nil afterDelay:1.0];
}
- (void)updateNumberLabelText {
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.nicmac.nextgame"];
NSString *date = [defaults stringForKey:#"MyNumberKey"];
self.numberLabel.text = [NSString stringWithFormat:#"%#", date];
NSString *doubleString = self.numberLabel.text;
double value = [doubleString doubleValue];
destinationDate = [NSDate dateWithTimeIntervalSince1970:value];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateNumberLabelText) userInfo:nil repeats:YES];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
int units = NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *components = [calendar components:units fromDate:[NSDate date] toDate:destinationDate options:0];
[hour setText:[NSString stringWithFormat:#"%ld", (long)[components day] * 24 + (long)[components hour]]];
[minute setText:[NSString stringWithFormat:#"%ld", (long)[components minute]]];
[second setText:[NSString stringWithFormat:#"%ld", (long)[components second]]];
}
Thanks!
The problem here lies in the - (void)updateNumberLabelText method.
Specifically, here:
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateNumberLabelText) userInfo:nil repeats:YES];
This means that every time you're calling this method, it's scheduling another timer to call this method every second (every second because repeats:YES). That means, every second (with a micro offset because of any lag in the computer), x number of scheduled timers is producing x MORE number of scheduled timers, meaning that every second you have double the number of timers. This is called exponential growth, and can be modelled to 2^x.
So to find the number of timers in 20 seconds:
2^1+2^2+2^3+2^4+2^5+2^6+2^7+2^8+2^9+2^10+2^11+2^12+2^13+2^14+2^15+2^16+2^17+2^18+2^19+2^20
Ew, can we represent this better - we can! We can use sigma notation like so:
<no. of seconds>
Σ 2^i
i=1
But enough math. That averages out to around a whopping 2,000,000 timers initialised in 20 seconds. That is a lot of timers, and the widget eventually struggles to catch breath (no more memory!) and dies/crashes/restarts itself.
Here's how to fix it:
If you really want this method to loop itself, call
[self performSelector:#selector(updateNumberLabelText) withObject:nil afterDelay:1.0];
in the method instead.
You could do it the way you're currently doing it and do repeats:NO instead, but I don't think that the timer will invalidate itself after the 1.0 second, and creating that many timers is not very efficient.

WebServices Call when application in Background and show the response in Local Notification

I have an enterprise application that I want to keep running, so it can call a webservice and inform to the user through Local Notification.
So, it now runs in the background, it makes the calls, gets results, informing to the user.
I Used a timer in applicationDidEnterBackground. My Code is,
- (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(#"ENTER BACKGROUND");
if (UIApplication.sharedApplication.applicationState == UIApplicationStateBackground)
{
backgroundTimer=nil;
[backgroundTimer invalidate];
backgroundTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(backgroundTasks) userInfo:nil repeats:YES];
}
application.applicationIconBadgeNumber = 0;
}
When the timer is triggered, i will check the date condition. If Date is matched then i have to make webservice call.
-(void)backgroundTasks
{
NSDate *CurrentDate = [NSDate date];
NSDate *previousDate = [[NSUserDefaults standardUserDefaults]valueForKey:#"ScheduledTime"];
NSLog(#"Current Date : %# - Previous Date : %#",CurrentDate,previousDate);
NSString *CurrentdateString = [NSString stringWithFormat:#"%#",CurrentDate];
NSString *PreviousdateString = [NSString stringWithFormat:#"%#",previousDate];
NSLog(#"Current Date String : %# - Previous Date String : %#",CurrentdateString,PreviousdateString);
if ([PreviousdateString isEqualToString:CurrentdateString])
{
NSLog(#"Both dates are same");
previousDate = [previousDate dateByAddingTimeInterval:60];
[[NSUserDefaults standardUserDefaults] setObject:previousDate forKey:#"ScheduledTime"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self updateUserLatLong:str_Latitude :str_Longitude]; //Webservices call
}
else
{
NSLog(#"Dates are different");
}
}
I got the response from this request, at that time i will create Local Notification. like this,
str_Condition = [NSString stringWithFormat:#"%#",[conditionarray lastObject]];
str_TempFaren = [NSString stringWithFormat:#"%#",[mutArr_High lastObject]];
NSLog(#"Condition : %#, Temperature : %#",str_Condition,str_TempFaren);
NSLog(#"SHOW ALERT NOTIFICATION");
NSLog(#"-----------------------------------------------------------------------");
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
localNotif.fireDate = [NSDate date];
localNotif.timeZone = [NSTimeZone defaultTimeZone];
localNotif.alertBody = [NSString stringWithFormat:#"%#° F - %#, See today's recommendations",str_TempFaren,str_Condition];
localNotif.alertAction = #"Reminder";
localNotif.soundName = UILocalNotificationDefaultSoundName;
localNotif.applicationIconBadgeNumber =0;
[[UIApplication sharedApplication]presentLocalNotificationNow:localNotif];
I Create Default Notification in another class, like this.
pickerDate = [datePicker date];
// Schedule the notification
UILocalNotification* localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = pickerDate;
localNotification.alertBody = #"See today's recommendations";
localNotification.alertAction = #"Reminder";
localNotification.timeZone = [NSTimeZone defaultTimeZone];
localNotification.repeatInterval = NSMinuteCalendarUnit;
// localNotification.applicationIconBadgeNumber = [[UIApplication sharedApplication] applicationIconBadgeNumber] + 1;
localNotification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
My Problem is, when the user remove the application from memory, obviously we can't make any webservices call. So when the app is removed from the memory, i want to show the message in localnotification which contains default alertbody. Now Both scenarios are working. but i want to display only one notification. if app removed from memory then only i have to show default notification. How to handle this problem, Please help me. i was strucked more than 7 hours for this issue.
I found the answer by myself. Solution is, I create the default UILocalNotification in the method,
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
NSDate *pickerDate = [[NSUserDefaults standardUserDefaults]valueForKey:#"ScheduledTime"];
UILocalNotification* localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = pickerDate;
localNotification.alertBody = #"See today's recommendations";
localNotification.alertAction = #"Reminder";
localNotification.timeZone = [NSTimeZone defaultTimeZone];
localNotification.repeatInterval = NSHourCalendarUnit;
localNotification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
application.applicationIconBadgeNumber = 0;
}
So, when i remove the app from memory. That time i create this notification.

Multipeer Connectivity stops working when user turns off and on bluetooth

I am using multipeer connectivity in ios7 in my app. The file sending and receiving works absolutely fine, but in the moment that a user accesses from within my app the control center (or even settings) and switches off either bluetooth or wifi, the file exchange stops working. When the user witches both of them back on, still it doesn't work. In order for them to work again, the user must close and re-open the app.
The files are sent in this way:
MCSession *session = [[MCSession alloc]
initWithPeer:key];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
dateComponents.year = 2100;
dateComponents.month = 1;
dateComponents.day = 1;
dateComponents.hour = 0;
dateComponents.minute = 0;
dateComponents.second = 0;
NSDate *referenceDate = [gregorian dateFromComponents: dateComponents];
NSDate *now = [NSDate date];
NSTimeInterval interval = [now timeIntervalSinceDate:referenceDate];
NSData *Recording = [NSData dataWithContentsOfFile:myFilePath];
NSString* str = [NSString stringWithFormat:#"%#.ext", button.titleLabel.text];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:str forKey:#"fileName"];
[dict setObject:#"Recording" forKey:#"fileType"];
[dict setObject:Recording forKey:#"FileData"];
NSData *myData = [NSKeyedArchiver archivedDataWithRootObject:dict];
[browser invitePeer:key
toSession:session
withContext:myData
timeout:interval];
The user can reload the devices at any time using:
[browser startBrowsingForPeers];
I think that the problem is the timeout, but I am not sure.
You need to re-init all the Multipeer Connectivity Framework related instances in the - (void)applicationDidBecomeActive:(UIApplication *)application app delegate method. That method is called when your app becomes active again after control center being opened and then closed.
I propose you instead monitor for changes to connectivity status, and teardown or re-init when you detect a change there. Here is my method for this same problem, using the excellent Reachability kit:
- (void)monitorReachability
{
// Allocate a reachability object
self.reachability = [Reachability reachabilityWithHostname:#"www.google.com"];
[[NSNotificationCenter defaultCenter] addObserverForName:kReachabilityChangedNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
Reachability *reachability = note.object;
if ( reachability.isReachable ) {
self.isPhysicallyConnected = YES;
[self updateNearbyService];
} else {
self.isPhysicallyConnected = NO;
[self tearDownNearbyService];
}
}];
// Start the notifier, which will cause the reachability object to retain itself!
[self.reachability startNotifier];
}
In teardown and update you would reconstruct your Multipeer stack.

How to update label once a day?

I'd like to have a label in my app that gets updated once a day at a specific time (i.e. 12 a.m.). If you could help me out I'd really appreciate it, thanks.
This is what i currently have but it only works if the app is opened between 12 and 1 am.
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"HH"];
NSString *stringDate = [formatter stringFromDate:[NSDate date]];
if ([stringDate isEqualToString:#"00"]) {
NSLog(#"its working");
[self changeLabel];
}
To clarify, I'm trying to change the text of a label once a day. I'd like to have the label update at a specific time like 12 am so that it updates with the start of each new day. I've tried a few ways of doing this but haven't gotten the desired result. If anyone knows of a way to do this I'd appreciate your input.
If you only need to determine the last time the application was opened, you can use NSUserDefaults to save the date and then check that the approprate number of seconds (86400 = 1 day) have passed.
Something along the lines of the following should point you in the right direction:
//gather last time app was opened
NSDate *lastUpdated = [[NSUserDefaults standardUserDefaults] objectForKey:#"app_last_updated"];
//check date is valid
if(lastUpdated)
{
//determine number of seconds that have passed
NSTimeInterval timeInterval = [lastUpdated timeIntervalSinceNow];
//check time interval is greater than 1 day
if(timeInterval > 86400)
{
//update label
}
}
//save current time
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:#"app_last_updated"];
It depends on what you want to do. On iOS, most likely your app is not running in the foreground all the time. So I would just update the label when the application is active in applicationDidBecomeActive:. Then I would create a timer to fire at the specific date/time I want.
For example:
NSDate *d = // create the next date/time
updateTimer_ = [[NSTimer alloc] initWithFireDate: d
interval: 0
target: self
selector:#selector(updateLabel:)
userInfo:nil repeats:NO];
NSRunLoop *runner = [NSRunLoop currentRunLoop];
[runner addTimer:updateTimer_ forMode: NSDefaultRunLoopMode];
I know this is an old post but a few days ago I needed just what Dave123 asked and I came up with this solution after searching a lot on the web:
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSLog(#"applicationDidBecomeActive");
self.masterviewController = [[PBMasterViewController alloc] init];
// 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.
NSLog(#"Seconds --------> %.f",[[NSDate date] timeIntervalSinceDate:[[NSUserDefaults standardUserDefaults] objectForKey:#"OLD_DATE"]]);
NSLog(#"old date: %#", [[NSUserDefaults standardUserDefaults] objectForKey:#"OLD_DATE"]);
// 24 hours must elapse between each update check
if ([[NSDate date] timeIntervalSinceDate:[[NSUserDefaults standardUserDefaults] objectForKey:#"OLD_DATE"]] > 86400) {
NSLog(#"Time elapsed since last update is MORE than 1 day. Check for updates.");
[self.masterviewController checkForUpdates];
} else {
NSLog(#"Time elapsed since last update is LESS than 1 day. Don't check for updates.");
}
// Here I convert the seconds into hours with two decimals, if you want to show the time elapsed in a UILabel
NSString *stringCheckInterval = [NSString stringWithFormat:#"%.02f hours since last update", [[NSDate date] timeIntervalSinceDate:[[NSUserDefaults standardUserDefaults] objectForKey:#"OLD_DATE"]] / 60 / 60];
NSLog(#"%#", stringCheckInterval);
}
is there someone that has the app opened form more than 24h??
Anyway, you can simply use an NSTimer with 86400 as delay time.
When you start the app, set up a timer that will fire at 12:00:01 AM and update your label.

custom ui local notification by user setting

I know how to make a local notification in the applicationDidEnterBackground function in AppDelegate.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UILocalNotification * uln = [[UILocalNotification alloc] init];
uln.fireDate = [NSDate dateWithTimeIntervalSinceNow:10];
uln.timeZone = [NSTimeZone defaultTimeZone];
uln.alertBody = #"Did you forget something?";
uln.alertAction = #"Show me";
uln.soundName = UILocalNotificationDefaultSoundName;
//uln.applicationIconBadgeNumber = 0;
uln.repeatInterval = NSMinuteCalendarUnit;
application.scheduledLocalNotifications = [NSArray arrayWithObject:uln];
}
But is that possible a user can set a time for the fireDate and a do not disturb time like late in midnight?
Yes you can set the firedate property to any date in the future. Do not disturb is possible for the user to trigger himself in Settings in iOS6.

Resources