Background location update in objective c - ios

I'm working on an app which will be used for tracking. For that I want periodic background location update, so I have used background mode location update and background fetch.
Now when the user moves a certain distance my delegate of background fetch i.e. PerformFetchWithCompletionHandler gets called but I have to initialize my location object and set a delegate of that inside this method, which didn't work.
-(void)entersBackground {
[(APP_DELEGATE).locationManager stopUpdatingLocation];
__block UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{bgTask=UIBackgrou‌​ndTaskInvalid; }];
if (timer) {
[timer invalidate]; timer=nil;
}
timer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(startTrackingBg) userInfo:nil repeats:YES];
}
-(void)startTrackingBg {
[(APP_DELEGATE).locationManager startUpdatingLocation];
}

Related

Keep just one background task

I'm developing an application that uses a background task to keep tracking of the user position every 20 seconds. All is fine, except that when I enter the application in background, a new background tasks is created, so that I can have in final multiple background tasks that are running.
I tried to add [[UIApplication sharedApplication] endBackgroundTask:bgTask]; in applicationWillEnterForeground, but that do nothing.
The point is that I want to invalidate/disable all running background tasks when I enter the app and create a new one when I enter in background, or to keep a just one background task running.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self runBackgroundTask:10];
}
-(void)runBackgroundTask: (int) time{
//check if application is in background mode
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
__block UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(startTracking) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
}
-(void)startTracking{
//Location manager code that is running well
}
I would suggest changing UIBackgroundTaskIdentifier to be a property of the app delegate class and initialize it to UIBackgroundTaskInvalid in didFinishLaunchingWithOptions. Then, in your other app delegate methods, you can just check the value of this property to determine whether there is a background task identifier to end or not.
--
An unrelated observation, but you don't need that runloop stuff. Just schedule the timer on the main thread/runloop (with scheduledTimerWithTimeInterval) and get rid of all of that runloop stuff (because you already added it to the main runloop and that runloop is already running).
For example, let's assume I wanted to do something every 10 seconds while the app was in background, I'd do something like the following:
#interface AppDelegate ()
#property (atomic) UIBackgroundTaskIdentifier bgTask;
#property (nonatomic, weak) NSTimer *timer;
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.bgTask = UIBackgroundTaskInvalid;
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
if (self.bgTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
}
}];
self.timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(startTracking) userInfo:nil repeats:YES];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// invalidate the timer if still running
[self.timer invalidate];
// end the background task if still present
if (self.bgTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
self.bgTask = UIBackgroundTaskInvalid;
}
}
- (void)startTracking{
NSLog(#"%s", __PRETTY_FUNCTION__);
}
Now, in your code sample, the timer wasn't a repeating timer, so if you only wanted to fire the timer once, then set repeats to NO, but then make sure that startTracking then ended the background task (no point in keeping the app alive if you're not going to do anything else).
BTW, make sure you run this code on a device, and do not run it from Xcode (as being attached to Xcode changes the background behavior of apps).
Specify location background mode
Use an NSTimer in the background by using UIApplication:beginBackgroundTaskWithExpirationHandler:
In case n is smaller than UIApplication:backgroundTimeRemaining ,it will work just fine, in case n is larger, the location manager should be enabled (and disabled) again before there is no time remaining to avoid the background task being killed.
This does work since location is one of the three allowed types of background execution.
Note: Did loose some time by testing this in the simulator where it doesn't work, works fine on phone.

NSTimer not working after reset

I have a NSTimer which should be work when app in foreground but should stop when app goes to background. but if came again to foreground timer is not working. Is there any way to do that?
inbox.m
(viewDidLoad){
myTimer = [NSTimer scheduledTimerWithTimeInterval: 0.60 target: self selector: #selector(saveSlow) userInfo: nil repeats: YES];
if([[NSUserDefaults standardUserDefaults] boolForKey:#"enterBG"]==YES)
{
[myTimer invalidate];
myTimer = nil;
}
....
}
Have you reset the bool value of enterBG after the app enter foreground? If not, even the timer is generated, it'll be invalidated again later.
Another point, you generated your timer in -viewDidLoad, generally, the view life loop only invoke it one time. The next time your app enter foreground, it won't be invoked. This might be another reason why your timer won't works again. You can try put it to -viewWillAppear: instead. You need to invoke the code snippet manually.
And a suggestion, you'd better to use the related notification to handle the timer:
UIApplicationDidEnterBackgroundNotification
UIApplicationWillEnterForegroundNotification
you cant try this code in your project
UIBackgroundTaskIdentifier bgTask = UIBackgroundTaskInvalid;
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.60 target:self selector:#selector(timerMethod) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
-(void) timerMethod{
NSLog(#"in Vijay yadav ");
}
Try to remove:
[myTimer invalidate];
myTimer = nil;
As the timer will stop working in background after 3mins. When you came to foreground, it automatically runs. By invalidating and nil, you are removing the instance of your timer. In this case you again have to intialize the timer.

Keep NSTimer running even application goes in background in ios

I want to perform particular task in background continuously even my application goes in background.
Here is the code which i tried. Timer is firing only once when it enters in background.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSTimer * timer = [NSTimer timerWithTimeInterval:2.0
target:self
selector:#selector(timerTicked)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer
forMode:NSDefaultRunLoopMode];
}
- (void) timerTicked
{
NSLog(#"Timer method");
}
You can't have a timer in background. It may work for a short time but your app will quickly goes into sleep mode if you don't have a registered background mode.
Available modes may be :
Audio
Location updates
Background fetch
Others ...
Take a look at Background execution documentation for more info
- (void)applicationDidEnterBackground:(UIApplication *)application {
UIBackgroundTaskIdentifier bgTask = UIBackgroundTaskInvalid;
bgTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}];
// NotificationTimer its timer
// myMethod1 call ur method
NotificationTimer = [NSTimer scheduledTimerWithTimeInterval: Interval
target: self
selector:#selector(myMethod1)
userInfo: nil repeats:YES];
}
I think its help to u

Update a UI from Background thread when app is also in Background to show Splash Screen on next resume

I want to show a Splash screen when app remain in background for more than 3 minutes. I started Background timer to trigger the function that will handle the UI update.
- (void)applicationDidEnterBackground:(UIApplication *)
if (application.applicationState == UIApplicationStateBackground) {
UIBackgroundTaskIdentifier bgTask = UIBackgroundTaskInvalid;
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
//and create new timer with async call:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
splashTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(timerFired) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:splashTimer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
}
Now when the timer is invoked i call the function to update my UI i.e adding an image in the window so make it look like that it is a Splash Screen.
- (void)timerFired
{
[splashTimer invalidate];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImageView *splash = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Splash_Screen.png"]];
splash.frame = self.window.bounds;
[self.window addSubview:splash];
}];
}
and then remove This image from the window when Application Enter foreground again
- (void)applicationDidBecomeActive:(UIApplication *)application
{
if ([[self.window subviews] count] > 1) {
[NSThread sleepForTimeInterval:1.0];
[[[self.window subviews] lastObject] removeFromSuperview];
}
}
but my Splash Screen is not shown when i call it from a background thread. I tried everything but no luck. My Problem is that My Timer is being invoked from a Background thread and if i want to add image from this thread the UI is not updated. Any Help?
Springboard does not update your app's snapshot[1] when all background tasks have expire/ended. IIRC when your application is in the background, the snapshot is only updated just before your app is suspended after handling these background events:
Background Fetch/Refresh
Remote Notification
(I don't recall the third one. Background Download maybe?)
[1] The image that appears in the task switcher and while your application is being moved back to the foreground.

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.

Resources