Having a NSTimer running background - ios

I've seen hundreds of solutions of how to get a NSTimer to run in the background.
I know that it is possible, just look at apps like Strava and Runkepper that tracks your time when working out.
But what is the best practice solution for doing so? I can't find one unison solution for this.
Also, I would like the NSTimer to be used across different UIViewControllers. How is this done as a best practice?
Thanks in regards! :)

NSTimers don't run in the background. Store the current time and the elapsed time of the timer when you got the background. When you come back to the foreground, you set up a new timer, using those two pieces of information to setup any state or data that needs to reflect the total elapsed time.
To share between viewCOntroller, just have one object implement this timer, and expose a property on it (e.g. elapsedTime) that gets updated every time interval . Then you can have the viewCOntrollers (that have a reference to that object) observe that property for changes.

You Can Try This Code in Your application NSTimers don't run in the background. acceding to apple But We Try forcefully Only 3 mint
AppDelegate.h
#property (nonatomic, unsafe_unretained) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
#property (nonatomic, strong) NSTimer *myTimer;
- (BOOL) isMultitaskingSupported;
- (void) timerMethod:(NSTimer *)paramSender;
AppDelegate.m
- (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.
if ([self isMultitaskingSupported] == NO)
{
return;
}
self.myTimer =[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(timerMethod:) userInfo:nil
repeats:YES];
self.backgroundTaskIdentifier =[application beginBackgroundTaskWithExpirationHandler:^(void) {
[self endBackgroundTask];
}];
}
pragma mark - NSTimer Process
- (BOOL) isMultitaskingSupported
{
BOOL result = NO;
if ([[UIDevice currentDevice]
respondsToSelector:#selector(isMultitaskingSupported)]){ result = [[UIDevice currentDevice] isMultitaskingSupported];
}
return result;
}
- (void) timerMethod:(NSTimer *)paramSender{
NSTimeInterval backgroundTimeRemaining =
[[UIApplication sharedApplication] backgroundTimeRemaining];
if (backgroundTimeRemaining == DBL_MAX)
{
NSLog(#"Background Time Remaining = Undetermined");
}
else
{
NSLog(#"Background Time Remaining = %.02f Seconds",backgroundTimeRemaining);
}
}
- (void) endBackgroundTask
{
dispatch_queue_t mainQueue = dispatch_get_main_queue(); __weak AppDelegate *weakSelf = self;
dispatch_async(mainQueue, ^(void) { AppDelegate *strongSelf = weakSelf; if (strongSelf != nil){
[strongSelf.myTimer invalidate];
[[UIApplication sharedApplication]
endBackgroundTask:self.backgroundTaskIdentifier];
strongSelf.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
} });
}

As pointed out in the comments, NSTimer won't work in the background, backround execution on iOS is quite tricky and only works in certain cases, check the Apple Docs on the topic, also this is an excellent read to acquire more background knowledge.
As for your case, it sound like you want to use UILocalNotification. As I understand from your comment:
I want to have a timer running while the app is not in the foreground. Just like Apples own timer app.
Apple's timer app uses UILocalNotification. It gives you a way to schedule a notification which will appear at a certain point in time to the user, regardless of whether the app is in the foreground or background! All you have to do in your app is schedule a notification, e.g. like this:
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.fireDate = dateTime;
localNotification.alertBody = [NSString stringWithFormat:#"Alert Fired at %#", dateTime];
localNotification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
Then iOS will handle the rest for you :)

Related

How change UIImageView in external screen on background mode

I need show changes in external screen with UIImageView when my ios app is on background mode.
I use this code to change the UIImageView
campaingTimer = [NSTimer scheduledTimerWithTimeInterval:timeFirstAd target:self selector:#selector(changeImage) userInfo:nil repeats:NO];
This works when my app is active, but when in background, enters the changeImage method, but not change the picture.
NSTimer selectors are not guaranteed to fire off in the background. Unless you're registering for specific permissions, such as playing music in the background, and whatever you're actually doing in the background is directly related to the permission you asked for, you should work under the assumption you will not be able to execute code while the app is backgrounded, as that'll set you up to succeed much better than trying to find workarounds.
In this scenario, it seems like you want to change the image after so much time passes. That NSTimer you have (assuming your methods are written correctly) will work while the app is in the foreground, but to deal with background I recommend listening for the appDidEnterBackground and appWillEnterForeground and posting notifications (see sample code below).
AppDelegate.m
================
- (void)applicationDidEnterBackground:(UIApplication *)application
{
self.currentTime = [NSDate date];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationNameForBecameActive object:nil userInfo:#{kUserInfoForBecameActive: self.currentTime}];
}
================
ViewController.m
================
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didBecomeActive:) name:kNotificationNameForBecameActive object:nil];
}
- (void)didBecomeActive:(NSNotification *)notification
{
NSDate *sleepDate = notification.userInfo[kUserInfoForBecameActive];
NSTimeInterval secondsPassed = [[NSDate date] timeIntervalSinceDate:sleepDate];
if (secondsPassed >= timeFirstAd)
{
[self changeImage];
}
// reinitialize NSTimer
}
================
Alternatively, you could post notifications for both appDidEnterBackground and appWillEnterForeground and save the time there, along with invalidating your NSTimer and restarting it.

iOS7 wrong screenshot in app switcher while in background

When my app is downloading big file and user switching to the other app, i'm running background task like this:
beginBackgroundTaskWithExpirationHandler:
and then if user opening "app switcher" by double click, screenshot of my app is completely random. Sometimes it's showing view controller that was not even open in the app.
ignoreSnapshotOnNextApplicationLaunch not helping, because it's not working at all.
Apple says: Avoid updating your windows and views here: documentation, but I'm not updating views.
I'm also running timer, to check how much background time is left, and this timer is the cause of my problems. If I'm not creating it, everything is working perfect, but I cannot save download state in Expiration handler - not enough time.
How can i avoid this weird behaviour?
-(void)appDidEnterBackground {
UIApplication *application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
if(backgroundTimer == nil || ![backgroundTimer isValid]) {
backgroundTimer = [[NSTimer alloc]
initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:0]
interval:1
target:self
selector:#selector(checkBackgroundTimeRemaining)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:backgroundTimer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
}
}
- (void)checkBackgroundTimeRemaining {
NSTimeInterval timeRemaining = [[UIApplication sharedApplication] backgroundTimeRemaining];
if(timeRemaining < 5) {
if(backgroundTimer != nil) {
[backgroundTimer invalidate];
backgroundTimer = nil;
}
[downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
[self saveResumeData:resumeData];
}];
}
}
Sometimes it's showing view controller that was not even open in the app.
This sounds really fishy and should never happen. Maybe you can add some code to show what you are doing?
ignoreSnapshotOnNextApplicationLaunch is irrelevant here since it's only used to determine what happens when the user taps on your icon again to open the app.
Did you maybe forget to call endBackgroundTask: when you've finished your background task?
I'm not sure what you intend with the timer? If it is to determine how much time is left for you to execute in the background, use UIApplication's backgroundTimeRemaining instead.

iOS reading data from server regularly

I need to create a iOS app where the app has to continuously check for the updates from the server(may be every 30 secs). But only when the app is running on the foreground.
I know this will drain the battery, but this will run on a environment where there's no internet. So we can't use push notifications.
Only option I can think of is sending a request to the server every 30 secs or so and get the updates. What is the best way to do this? Using NSTimer and NSURLConnection or any other better approaches?
Also if I use a timer, when the app goes to the background will it pause and will it start running as it comes to the foreground again? Is there a chance that app get killed while its on background?
Thanks
Using NSTimer and NSURLConnection or any other better approaches?
My first thought was also to use NSTimer and NSURLConnection.
Also if I use a timer, when the app goes to the background will it pause and will it start running as it comes to the foreground again?
Yes, it will. It doesn't exactly pause, but based on my testing in the simulator, the effect is similar. Let's say the timer is set to go off at 00:00:00, 00:00:30, 00:00:60, ... and you background the app at 00:00:15 and resume it at 00:00:45. The timer that was supposed to fire at 00:00:30 fires immediately when you resume (at 00:00:45), and the next firing (at 00:00:60) and subsequent firings are back on schedule.
Is there a chance that app get killed while its on background?
Yes, there is. But if you start the timer whenever the app launches, this shouldn't be a problem, right?
Your best bet is to setup a separate object that manages these operations on a background thread. Then in your app delegate, when
- (void)applicationWillResignActive:(UIApplication *)application
is called, have this special object stop all of it's synchronizing and clean up anything it needs to.
Then when:
- (void)applicationDidBecomeActive:(UIApplication *)application
gets called as the app gets active again, signal your object to query / poll on its background thread again.
Your custom object could have an interface like this
#interface PollingObject : NSObject
{
NSTimer* _timer;
NSUinteger _interval;
BOOL _cancel;
BOOL _isPolling;
dispatch_queue_t _pollQueue;
}
- (void)startPolling;
- (void)stopPolling;
#end
The implementation can be something like this:
#implementation PollingObject : NSObject
- (id)init
{
if (self = [super init])
{
_interval = 1; // 1 second interval
_cancel = NO; // default to NO
_isPolling = NO; // default to NO
// init your background queue
_pollQueue = dispatch_queue_create("com.yourconame.yourappname.pollQueue", NULL);
}
return self;
}
- (void)heartbeat
{
if (_cancel)
{
// stop the timer
[_timer invalidate];
_isPolling = NO;
return;
}
// Runs the polling method ONCE on a background queue
dispatch_async(_pollQueue, ^{
[self pollingMethod];
});
}
- (void)pollingMethod
{
// Do actual network polling work here...but only run it once. (don't loop)
}
- (void)startPolling
{
_cancel = NO;
if (_isPolling)
{
NSLog(#"Already polling");
return;
}
// schedule the method heartbeat to run every second
_timer = [NSTimer scheduledTimerWithTimeInterval:_interval target:self selector:#selector(heartbeat) userInfo:nil repeats:YES];
}
- (void)stopPolling
{
// we set the flag here and the next second the heartbeat will stop the timer
_cancel = YES;
}
#end
Look at Rocket real-time networking which looks easy to setup through AFNetworking 2.0.
https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-2.0-Migration-Guide
See the last part of this wiki. I have not used it but it would be something I would try if I had your requirements.

How to run a background HTTP process in ios?

ios noob here: I have an ipad app as an In/Out board posted in the office. Basically, it runs one app; a TableView of 14 people and whether they are in or out of the office. At their desks, people hit a button on a web page to indicate their status when they leave for lunch, meeting or whatever. The ipad app then contacts our webserver every 5 minutes to retrieve an updated status list.
I've found a couple old postings on Stack, one here, which says all downloading must happen in the foreground of the application. The post is from 2011 so wondering if things have changed? I would rather not have the UI locked-up every 5 minutes if someone wants too look at the bottom of the list while a refresh is happening.
That post is about the app being in the background, your use case suggests someone is using the app, and it is in the foreground. You can of course do a web request on a background thread without locking the UI thread. The general pattern for your scenario is, when the view appears or the app becomes active, refresh the data (on a background thread), refresh the table (on the main thread), and then set your timer for an automatic refresh (and disable it when the app goes into the background), and potentially implement some kind of 'pull to refresh' feature (https://github.com/enormego/EGOTableViewPullRefresh).
If you do those things, your data will be up to date when people are viewing the app, and users can guarantee it by pulling to refresh.
Yes! Things have changed. It's now possible (as of iOS 7) to run HTTP requests while the app is backgrounded.
In order to do so, you need to add the value fetch to your app's UIBackgroundModes Info.plist key.
For more details see the iOS App Programming Guide.
After looking through a lot of code and a dizzying array of ways to do this, I really couldn't find a "simple" example. Many examples on the net are pre-ARC, or too complex for my level of understanding. Still other examples hinged on 3rd party libraries which are no longer in development. Still other examples, more up to date, have timeouts of 30 seconds in which everything must be completed (ios7 fetch) which doesn't seem like enough time for a quick download on a busy wi-fi network. Eventually, I did manage to piece together a working sample which does run a background download every 20 seconds. Not sure how to update the UI yet.
AppDelegate.m
#import "bgtask.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
bgtask *b = [[bgtask alloc] initTaskWithURL:#"http://www.google.com" app:application];
return YES;
}
bgtask.h
#import <Foundation/Foundation.h>
#interface bgtask : NSOperation
#property (strong, atomic) NSMutableData *webData;
#property (strong, atomic) UIApplication *myApplication;
- (id) initTaskWithURL:(NSString *)url app:(UIApplication *)application;
#end
bgtask.m
#import "bgtask.h"
#implementation bgtask
UIBackgroundTaskIdentifier backgroundTask;
#synthesize webData = _webData;
#synthesize myApplication = _myApplication;
NSString *mURL;
// connect to webserver and send values. return response data
- (void) webConnect
{
NSURL *myURL = [NSURL URLWithString:mURL];
_webData = [NSData dataWithContentsOfURL:myURL];
if (_webData)
{
// save response data if connected ok
NSLog(#"connetion ok got %ul bytes", [_webData length]);
}
else
{
NSLog(#"connection failed");
//TODO: some error handling
}
}
- (void) timerTask:(NSTimer *) timer
{
backgroundTask = [_myApplication beginBackgroundTaskWithExpirationHandler:
^{
dispatch_async(dispatch_get_main_queue(),
^{
if (backgroundTask != UIBackgroundTaskInvalid)
{
[_myApplication endBackgroundTask:backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
}
});
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
NSLog (#"Running refresh...");
[self webConnect];
dispatch_async(dispatch_get_main_queue(),
^{
if (backgroundTask != UIBackgroundTaskInvalid)
{
[_myApplication endBackgroundTask:backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
}
});
});
}
- (id) initTaskWithURL:(NSString *)url app:(UIApplication *)application
{
self = [super init];
if (self)
{
// setup repeating refresh task.
// Save url, application for later use
mURL = [[NSString alloc] initWithString:url];
_myApplication = application;
[NSTimer scheduledTimerWithTimeInterval:20.0
target:self
selector:#selector(timerTask:)
userInfo:nil
repeats:YES];
NSLog (#"task init");
}// if self
return (self);
}

autolocking ios device, NSTimer and scheduledTimerWithTimeInterval

I would like to trigger action after some ammount of time (in production that will be 30 minutes) and right now I'm using NSTimers scheduledTimerWithTimeInterval. During tests (with timeout being 20 seconds, not 1800 seconds) everything seems fine. In debbuging mode (running from XCode) everything seems fine as well, because the device does not autolock. But in real life, when application is ran on device, autolocking (precisely autolocking, triggering lock button does not) "freezes" the timer (or at least moves the timer trigger somehow to future).
Can I handle that situation? Of course I can disable idleTimer in UIApplication sharedApplication but when application will enter background mode ipad still can autolock.
Actually you can,
First of all, create a default value for "startTimeOfMyExoticTimer" to NSUserDefaults:
[[[NSUserDefaults] standardDefaults] setObject:[NSDate date] forKey:#"startTimeOfMyExoticTimer"];
Then kick of a timer to check if the valid time range is over:
[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(checkIfTimeIsOver) userInfo:nil repeats:YES];
Then implement your checker method:
-(void)checkIfTimeIsOver{
NSDate * now = [NSDate date];
NSDate * startTime = [[NSUserDefaults standardDefaults] objectForKey:#"startTimeOfMyExoticTimer"];
// Here compare your now and startTime objects. (subsract them) and see the difference between them. If your time range is to be set on 30 seconds. Then you should check if the time difference between those objects are bigger than 30 seconds.
}
This will be working even if the device is locked, the app is in background etc.
This approach worked for me, hope it'll work for you too
//Implement this block help your application run background about 10 minutes
#interface AppDeledate(){
UIBackgroundTaskIdentifier backgroundTaskId;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// if missing interval exist > start timer again and set missing interval to 0
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"Did Enter Background");
backgroundTaskId = [application beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background Task Expired !!!\n");
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
//save missing interval to NSUserDefault
}];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
NSLog(#"Will Enter Foreground");
if (backgroundTaskId && backgroundTaskId != UIBackgroundTaskInvalid) {
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
}
}

Resources