In didFinishLaunchingWithOptions, how do you know if the app is resuming from the foreground or is a new launching? I need to know in this method because there are 2 tasks to run depending on what the launchOptions are and what state the app is in. Thank you.
From Test if app did become active from a UILocalNotification:
When an app enters the foreground from the background it does not trigger applicationDidFinishLaunchingWithOptions. It does, however, call applicationWillEnterForeground and applicationDidBecomeActive. This can be verified with a couple of NSLogs.
So it's possible to know if the app returns from background or if it's a new launch.
From the developer page: search for didFinishLaunchingWithOptions and you will have all the options. Also, they have this diagram telling all the stages that the app has:
What you are looking is for the Inactive State:
The app is running in the foreground but is not receiving events. (It may be executing other code though.) An app usually stays in this state only briefly as it transitions to a different state.
Upon entering this state, the app should put itself into a quiescent state with the expectation of moving to the background or active state shortly.
Or the Suspended State:
The app is in memory but is not executing code. The system suspends apps that are in the background and do not have any pending tasks to complete. The system may purge suspended apps at any time without waking them up to make room for other apps.
After reading this I don't think that what you need is Background State
didFinishLaunchingWithOptions method calls only with a new launch. It can be regular launch in foreground, or it can be app relaunching in background when you use Background Modes. To define the details of launching see the launchOptions.
When app goes into background or foreground see applicationDidEnterBackground and applicationWillEnterForeground methods.
Here is how Branch defines launch options (https://github.com/BranchMetrics/iOS-Deferred-Deep-Linking-SDK/blob/master/Branch-SDK/Branch-SDK/Branch.m) :
- (void)initSessionWithLaunchOptions:(NSDictionary *)options isReferrable:(BOOL)isReferrable explicitlyRequestedReferrable:(BOOL)explicitlyRequestedReferrable automaticallyDisplayController:(BOOL)automaticallyDisplayController {
self.shouldAutomaticallyDeepLink = automaticallyDisplayController;
self.preferenceHelper.isReferrable = isReferrable;
self.preferenceHelper.explicitlyRequestedReferrable = explicitlyRequestedReferrable;
if ([BNCSystemObserver getOSVersion].integerValue >= 8) {
if (![options objectForKey:UIApplicationLaunchOptionsURLKey] && ![options objectForKey:UIApplicationLaunchOptionsUserActivityDictionaryKey]) {
[self initUserSessionAndCallCallback:YES];
}
else if ([options objectForKey:UIApplicationLaunchOptionsUserActivityDictionaryKey]) {
self.preferenceHelper.isContinuingUserActivity = YES;
}
}
else {
if (![options objectForKey:UIApplicationLaunchOptionsURLKey]) {
[self initUserSessionAndCallCallback:YES];
}
}
}
Related
I am trying to run some code during the applicationWillResignActive when the user opens the task switcher and it has worked fine until I began using bluetooth in my app.
When bluetooth tries to connect to a device it shows an alert window asking if the user wants to pair the device. This alert is enough to trigger the applicationWillResignActive method and then runs my code for when the app is being navigated away from (task switcher). This causes a large problem since the code I intend to run when switching away, turns off some much needed functionality within the actual app. So once they press "pair" or "cancel" on that alert, all of my app stops functioning as it should because the app has lost focus.
I have tried to detect the state of the application during this time with this... NSUInteger state = [[UIApplication sharedApplication] applicationState]; thinking of course that it would be considered active when the alert pops up and inactive when in the task switcher. However, this was not the case it shows up as active for both use cases.
Update #1
The question...
How can I differentiate in the application between the app causing a system level inactive focus state like running code to connect to bluetooth, versus the user causing the system level inactive focus like double tapping the home button? All in the efforts to distinguish what is causing the applicationWillResignActive method to fire.
Update #2
The intention of this functionality is to set a flag in NSUserDefaults when bluetooth connects to the device. This flag is being "observed" and used to trigger the changing of view controllers to a page related to this new BT connection. When the user double presses the home button and moves to task switcher I turn off BT and switch to iBeacon so I can notify of events. All is well with this current implementation all bar 1 use case.
If the user hasn't yet connected to the BT device and it connects for the first time and that pairing alert comes up it fires the applicationWillResignActive method just the same as double tapping the home button does. In this method the code then checks for that NSUserDefaults flag to see if it switched on (which by this time it is because the BT has already reached the CBCentralManager's didConnectPeripheral method and turned it on) and if it's on, it turns off BT and switched to scanning for iBeacon. Because the app is still open this obviously causes problems. The app is running so the user see's the BT connect, the new view slide in, the pairing alert come up, then the new view slide right back out and iBeacon starts sending notifications intended for when the user is in the task switcher.
I already have this exact functionality happening in the applicationWillEnterBackground method so that's not the answer. I need to have a way of saying "the app is running right now and we've received an alert instead of double tapping home, so please don't turn off BT and turn on iBeacon yet"
Two possible solutions:
1. The answer may lie in this statement:
When bluetooth tries to connect to a device it shows an alert window asking if the user wants to pair the device.
Your app must do something to cause this alert to appear. You could set a Date field to the current time in your AppDelegate when this happens, and then when you get a call to applicationWillResignActive you can compare that timestamp to the current time, and if it is < 1 second or so, you have a pretty good clue that the bluetooth dialog went up.
Of course, this is not foolproof. As #danh notes in his comment, the design of iOS makes this really difficult. You won't know for sure if the bluetooth dialog went up, or if the user or OS just happened to bring something else to the foreground at the same time. What's more, it's always possible that even if the bluetooth dialog comes up, the user might decide at that very moment to go check his or her email or start browsing Facebook. In that case, it is both true that the bluetooth dialog is what sent your app to the background, AND the user navigated away from the app. Unfortunately, iOS doesn't really give you a way to differentiate the two.
2. You might use a background task to handle your cleanup logic.
You can request up to 180 seconds of background running time after the call to applicationWillResignActive, so you could defer your cleanup tasks until say 175 seconds have passed since your app is resigned to the background. If the user doesn't come back within 3 minutes, it's probably time to do this cleanup anyway. My blog post here shows the basics of setting up a background task. It is specifically targeted to extending beacon ranging time, but you can put whatever logic you want inside the background code block like this:
- (void)extendBackgroundRunningTime {
if (_backgroundTask != UIBackgroundTaskInvalid) {
// if we are in here, that means the background task is already running.
// don't restart it.
return;
}
NSLog(#"Attempting to extend background running time");
__block Boolean self_terminate = YES;
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:#"DummyTask" expirationHandler:^{
NSLog(#"Background task expired by iOS");
if (self_terminate) {
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
_backgroundTask = UIBackgroundTaskInvalid;
}
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Background task started. Waiting 175 seconds before cleanup.");
[NSThread sleepForTimeInterval:175];
//TODO: perform cleanup code if app is not in the foreground by now
});
}
Is there A way to guarantee that the applicationWillTerminate method in the AppDelegate delegate will be hit? Something like a key in the info.plist file, etc..?
My goal: I'm working in a beacon app, the piece of code is in this article. My problem is that the message from the didEnterRegion keeps poping even when i'm beside the beacon. To solve that I'm setting a flag to control the message. My code below:
if(!UserDefaults.standard.bool(forKey: Constants.EnterZoneMsgShowName)){
let notification = UILocalNotification()
notification.alertBody = "Hi, you are about to arrive at CIDMA's office. Please open de demo app and turn on the bluetooth on your device to enrich your experience. "
UIApplication.shared.presentLocalNotificationNow(notification)
UserDefaults.standard.set(true, forKey: Constants.EnterZoneMsgShowName)
}
I want to set this flag to false when I close the app. I tried to put it at the applicationWillTerminate but this method is not hit every time.
I would like to know how to guarantee that this code will be hit or if there is a better place to put the code: UserDefaults.standard.set(false, forKey: Constants.EnterZoneMsgShowName)
applicationWillTerminate(_:) - Tells the delegate when the app is about
to terminate.
For apps that do not support background execution or are linked against iOS 3.x or earlier, this method is always called when the user quits the app.
For apps that support background execution, this method is generally not called when the user quits the app because the app simply moves to the background in that case. However, this method may be called in situations where the app is running in the background (not suspended) and the system needs to terminate it for some reason.
What you want to call is applicationDidEnterBackground if your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
Sorry if this question is too basic, but I'm stuck.
Scenario:
My app uses background and suspended locationUpdates using startMonitoringSignificantLocationChanges.
in my AppDelegate I have:
if let options = launchOptions {
locationKey = options[UIApplicationLaunchOptionsLocationKey] as! Bool
}
So when app is launched by location manager, my locationKey = true.
I was hoping to use locationKey inside the app to distinguish location manager launching or user launching.
The problem is:
When the app is launched by location manager, the behavior is exactly as when user launch the app, I mean, all views (from launch process) are instantiated, but app still "closed".
When I tap to open the app, it is "like" is already opened, so, I can't verify my locationKey, because it is always true and AppDelegate (didFinishLaunchingWithOptions) is not triggered again and then is quite hard to figure out how I can process the startup in its different ways, user and location.
My question is, how can I identify that the app was launched by location and when user really open the app I "restore" its normal way ?
Thank you and sorry if it's not clear
can you be a bit more clear about the behaviour you expect for your app?
The app won't ever be launched by location :
once the user launch the app (triggering didFinishLaunchingWithOptions),
the app can go into background mode (aka being suspended) and can do some work (like updating locations, finishing some tasks, or delegating unfinished downloads to the system so then can finish) or is killed.
When the user come back to the app either the app was in background and switch to foreground mode or it was killed and didFinishLaunchingWithOptions will be invoke again to relaunch the app completly.
During a background task execution what will happen if the user kills the app (which already is in background mode)?
Picture this:
The app starts task X (with 10 min background support and a expiration handler that has to be called).
Then, the app goes to background and the user kills the app.
I'm confused on what will happen to task X after the app is killed. Does it still have that background time to execute? Does the expiration handler ever gets called?
If the app is "already in background", the user has already "closed the app"! So what can your question mean? You've already gone into the background, and if you called beginBackgroundTaskWithExpirationHandler: as you did so, things proceed normally.
Do you mean the user forcibly kills the app in the background, by summoning the "recent apps" interface and going into jiggy mode and deleting the app from the "recent apps" interface? Then the app is summarily killed; you get no notification and whatever you were doing is interrupted.
Moreover, the only thing the expiration handler block is supposed to do is call endBackgroundTask:. If you are summarily killed, the fact that you are unable to make this call is unimportant!
Ok so this is the result
In this case OS will send a SIGKILL signal to your app's process and applicationWillTerminate method is not called.
Below is just my interpretation from Apple docs, guess work and Google results.
In such case below method of your application delegate will get called
- (void)applicationWillTerminate:(UIApplication *)application
Quote from Apple Docs
For applications that do not support background execution or are
linked against iOS 3.x or earlier, this method is always called when
the user quits the application. For applications that support
background execution, this method is generally not called when the
user quits the application because the application simply moves to the
background in that case. However, this method may be called in
situations where the application is running in the background (not
suspended) and the system needs to terminate it for some reason.
So you have to UIApplicationExitsOnSuspend value to YES in your plist file otherwise there is no guarantee that applicationWillTerminate: will ever get called. That is why the doc has may used in.
I dont think expiration handler block will be called, though I am not sure.
This is pretty easy to test, so I just did (on an iPhone 4S running iOS 6.1.3) using code I'll paste at the end, which starts a background task in the app delegate's applicationDidEnterBackground method.
The result is surprising.
When the user exits the app by clicking the Home button, then manually kills the app (by double-clicking Home, putting things in jiggy mode and hitting the app's close icon) the following occurs:
applicationWillTerminate is called.
When applicationWillTerminate exits, background execution is terminated, regardless of how much execution time the background task had left. The app has been killed.
HOWEVER..
If you arrange things so that applicationWillTerminate doesn't exit after being called, as in my code below, the following happens -- at least it does on my test setup -- when the app is manually killed:
The app's background task continues to run in the background.
Even when the allocated background execution time has expired, the background task continues to run, as does the code in applicationWillTerminate, until that method exits.
This is clearly a bug -- you shouldn't be able to continue to run code forever -- and I wouldn't rely on it always working. But those who've been using various hacks around playing audio in the background to keep an app alive might want to investigate. I'd be interested if other people try the code on different iOS versions/devices and get the same results.
Code for AppDelegate.m in my test project:
//
// BTAppDelegate.m
// BackgroundTest
//
// Created by David Fearon on 07/05/2013.
// Copyright (c) 2013 David Fearon. All rights reserved.
//
#import "BTAppDelegate.h"
#implementation BTAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(#"application didFinishLaunchingWithOptions called");
// Override point for customization after application launch.
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(#"applicationWillResignActive: called");
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"applicationDidEnterBackground: called");
UIApplication* thisApp = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier __block task = [thisApp beginBackgroundTaskWithExpirationHandler:^{
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self printTimeRemaining];
while(YES) {
[NSThread sleepForTimeInterval:1.0];
[self printTimeRemaining];
}
//[thisApp endBackgroundTask:task];
});
}
-(void)printTimeRemaining{
NSLog(#"Background task time remaining: %f", [[UIApplication sharedApplication] backgroundTimeRemaining]);
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
NSLog(#"applicationWillEnterForeground: called");
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSLog(#"applicationDidBecomeActive: called");
}
- (void)applicationWillTerminate:(UIApplication *)application
{
NSLog(#"applicationWillTerminate: called");
while(YES) {
[NSThread sleepForTimeInterval:1.0];
NSLog(#"Still executing code in applicationWillTerminate.");
}
}
#end
I am trying to keep the iOS app in active state for more than 10 mins when it enters in background state.
How can I implement this.
See "Background Execution" section of the iPhoneAppProgrammingGuide. In short, your app must be one of these types:
Apps that play audible content to the user while in the background, such as a music player app
Apps that keep users informed of their location at all times, such as a navigation app
Apps that support Voice over Internet Protocol (VoIP)
Newsstand apps that need to download and process new content
Apps that receive regular updates from external accessories
And you must add to the Info.plist as follows:
Add the UIBackgroundModes key to your
Info.plist file and set its value to an array containing one or more of the following strings:
audio—The app plays audible content to the user while in the background. (This content includes streaming audio or video content using AirPlay.)
location—The app keeps users informed of their location, even while it is running in the background.
voip—The app provides the ability for the user to make phone calls using an Internet connection.
newsstand-content—The app is aNewsstand app that downloads and processesmagazine or newspaper
content in the background.
external-accessory—The app works with a hardware accessory that needs to deliver updates on a
regular schedule through the External Accessory framework.
bluetooth-central—The app works with a Bluetooth accessory that needs to deliver updates on a
regular schedule through the CoreBluetooth framework
Note that part of the review process will be checking to make sure that your app does what it says it's doing with regard to background processing.
Here's what I've done using beginBackgroundTaskWithExpirationHandler.
Write a method that starts a background task.
Inside that background task, run a NSTimer with a scheduled (non repeating) time that is under 10 minutes. For the purposes of my situation I was using 5 minutes.
Once the NStimer's selector fires, end the background task and then instantly call the method that you wrote earlier to start off another background task.
If you want to schedule methods to run at specific times, you will have to check for them in the background task.
This solution isn't really ideal and is still power hungry but will do what you want.
Edit: Since iOS7, I suggest you read this excellent post. Note that this article was last updated in 2013 and is probably irrelevant now.
Only certain types of apps are allowed to run in the background. See the "Implementing Long-Running Background Tasks" section of this guide.
If you aren't requesting permissions to do background processing you can use UIApplication's beginBackgroundTaskWithExpirationHandler but you cannot get extra time.
This code makes your iOS app run indefinitely in the background. Copy and paste the below methods into a singleton / manager which handles the tasks you need to perform in the background.
// #interface
// Declare Private property
#property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
//#end
// ...
// Copy into
//#implementation
- (void)setupBackgrounding {
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(appBackgrounding:)
name: UIApplicationDidEnterBackgroundNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(appForegrounding:)
name: UIApplicationWillEnterForegroundNotification
object: nil];
}
- (void)appBackgrounding: (NSNotification *)notification {
[self keepAlive];
}
- (void) keepAlive {
self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
[self keepAlive];
}];
}
- (void)appForegrounding: (NSNotification *)notification {
if (self.backgroundTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid;
}
}
You can't. Unless your app uses audio, voip or gps. What you can do is notify the user (via local notifications) that the time is almost up and ask him to open/close the app.
Also if you just need to notify the user, you can use push notifications.
https://github.com/yarodevuci/backgroundTask Check my code here I am using audio player that plays blank wav file Works perfectly on IOS 8 Battery usage around 10% in 24 hour period
How to use:
var backgroundTask = BackgroundTask()
backgroundTask.startBackgroundTask() //Starts playing blank audio file. You can run NSTimer() or whatever you need and it will continue executing in the background.
backgroundTask.stopBackgroundTask() //Stops the task
Warning: Apple will reject this if you try to submit it!
If your App type is not one of VOIP/Audio/Location....(check Background Modes),
or you don't want to specify your App as a background App, you can implement beginBackgroundTaskWithName:expirationHandler or beginBackgroundTaskWithExpirationHandler to ask for more time to run your process in background. You can find the detailed description here
Apps moving to the background are expected to put themselves into a quiescent state as quickly as possible so that they can be suspended by the system. If your app is in the middle of a task and needs a little extra time to complete that task, it can call the beginBackgroundTaskWithName:expirationHandler: or beginBackgroundTaskWithExpirationHandler: method of the UIApplication object to request some additional execution time. Calling either of these methods delays the suspension of your app temporarily, giving it a little extra time to finish its work. Upon completion of that work, your app must call the endBackgroundTask: method to let the system know that it is finished and can be suspended.
Each call to the beginBackgroundTaskWithName:expirationHandler: or beginBackgroundTaskWithExpirationHandler: method generates a unique token to associate with the corresponding task. When your app completes a task, it must call the endBackgroundTask: method with the corresponding token to let the system know that the task is complete. Failure to call the endBackgroundTask: method for a background task will result in the termination of your app. If you provided an expiration handler when starting the task, the system calls that handler and gives you one last chance to end the task and avoid termination.