Some background
I am currently writing a UI Test for a settings pane, and click on buttons to enable certain permissions such as push notifications and location services.
However, if the alert for the permission has been displayed before (regardless of the user allowing or denying access to the permission), the alert will not display again, and will just take the user to the settings app. Unfortunately, these settings do not reset, meaning the first time I run the UI tests, alerts will show; and on all subsequent UI test runs, the buttons will take me to the settings app unless I reset the device before the tests begin.
My issue
Thus, my test needs to know if the app went into the background, and attempt to foreground it to continue the testing:
if app.state == background {
foregroundApp()
}
// continue with other tests
Is there any way to determine if the app is in the background?
What I tried
I researched methods to determine the state of the application (running/background/etc) from a UI test, and was not able to find much. I tried to check whether certain elements exist:
if (app.navigationBars.element.exists) ...
but this gives me runtime errors[1] if the user is taken to the settings page because the app under test is in the background, and the test cannot lookup the navigationBars (or other elements).
I tried using some of the methods from Facebook's private headers for XCUIApplication() and XCUIElement().
XCUIApplication().state always returns 3 no matter what state the app is currently in, and any attempts to call XCUIApplication().resolve() to foreground the app give me the same errors as before[1]
I tried to rewrite the logic to foreground the app before resuming the tests, but methods such as XCUIApplication().launch() kill the app before restarting, which I cannot do. Only siri service seems to work for me, but I cannot access the siri service through the corporate proxy, and modifying proxy permissions is not possible.
Is there any other way to check the app state?
Errors
[1] This error is printed every time I try to do something involving state. I do not call snapshotView anywhere, and thus the suggestion to use afterScreenUpdates is useless.
Failure to get snapshot within 15.0s
Cannot snapshot view (<UIKeyboardImpl: 0x7febcc75d000; frame = (0 0;
414 226); layer = <CALayer: 0x608000625720>>) with
afterScreenUpdates:NO, because the view is not in a window. Use
afterScreenUpdates:YES.`
tl;dr
I need to check whether the app I am UI testing has entered the background (i.e. user pressed the home button). Checking for existence of particular elements such as navigation bars doesn't work, neither do most methods from Facebook's private headers for XCUIApplication/XCUIElement. Foregrounding the app also causes issues, and relaunching the app is not an option; neither is siri service.
You can do this in Swift 4, using XCUIApplication.state, which will give you information about the state of the app - whether it's in the foreground or background etc. however, it's not possible to find this information in Swift 3 and below. Essentially, UI testing in Swift 3 doesn't support leaving the app.
I am trying to maintain a MultipeerConnectivity "session" when the application enters temporarily in the background, so I thought about using a background task as I've seen few times here ... The problem is I have no idea how to "maintain" the session with the UIBackgroundTask, can someone please post a hint
I don't care about the advertisers/browsers, it's okay to stop them, but I'd like the session to not disconnect as reconnecting is super buggy for the moment.
As per apple documentation "If the app moves into the background, the framework stops advertising and browsing and disconnects any open sessions. Upon returning to the foreground, the framework automatically resumes advertising and browsing, but the developer must reestablish any closed sessions" Refer: Apple doc
One way of extending the connection is as follows
Answering my own question, hoping it would help people in the same situation.
For people new to iOS development, "using a background service" simple means turning on the "Background Modes" option in the "Capabilities" tab of your target.
That alone should give your app around 10 minutes life in the background before it gets killed.
But, when the app goes to background, I use the "backgroundTimeRemaining" to know how much time I have left, it just starts at 180 (in sec, so 3 minutes), yet, the printing loop did continue to work passed three minutes, which means there is a need to manually code what should happen when the time is reached.
For Multipeer Connectivity, this is enough to maintain the connection alive when the app enters background, and it will still receive all messages/streams without a problem.
For the sake of stability, I do some cleaning as follow:
In the appDelegate.h
#property (nonatomic) UIBackgroundTaskIdentifier backgroundTask; //declaring a background task
In the appDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application
{
self.backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^
{
//This is called 3 seconds before the time expires
//Here: Kill the session, advertisers, nil its delegates,
// which should correctly send a disconnect signal to other peers
// it's important if we want to be able to reconnect later,
// as the MC framework is still buggy
[application endBackgroundTask:self.backgroundTask];
self.backgroundTask = UIBackgroundTaskInvalid; //Invalidate the background task
}];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Here: We should init back the session, start the advertising and set the delegates from scratch
// This should allow the app to reconnect to the same session with more often than not
self.backgroundTask = UIBackgroundTaskInvalid; //Here we invalidate the background task if the timer didn't end already
}
I've asked this same question once on the apple developer forums. One of the Apple employees told me that basically all of the Multipeer connectivity should be considered off-limits when your app is not int the foreground.
I have a voip app and it needs to run in the background. To my understanding these are the things I need to do:
Flag the app as voip.
Set the 'application does not run in background' flag to NO.
Set an expiration handler, a piece of code that extends the standard 10 minutes of execution time you get.
More?
I set both flags in the info.plist file and I get my 10 minutes. I tried what is suggested in this post. Here is my code:
//in didFinishLaunchingWithOptions:
expirationHandler = ^{
NSLog(#"ending background task");
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
NSLog(#"restarting background task");
bgTask = UIBackgroundTaskInvalid;
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:expirationHandler];
NSLog(#"finished running background task");
};
//in applicationDidEnterBackground
NSLog(#"entering background mode");
bgTask = UIBackgroundTaskInvalid;
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:expirationHandler];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// inform others to stop tasks, if you like
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyApplicationEntersBackground" object:self];
//this while loop is just here for testing
inBackground = true;
while (inBackground) {
NSLog(#"stayin alive!!"); //this keeps going forever
sleep(10);
}
});
The situation:
I use a third party library that handles the communication with our webservice. The service is a CommuniGate pro server. I receive presence updates (online/offline) and instant messages from contacts via the library. The library is CommuniGate's ximss library, a protocol they made which is similar to xmpp and is used for xml-based sip requests, as well as IM and presence. When the user logs in to the app, he sees his contacts (CmmuniGate friends list) and he can choose to call one. After a ximss verification message has been sent and the other side accepted the call it logs the start time of the call and starts a facetime call.
The problem:
When the app enters the background by pressing the home button, I start seeing the 'stayin alive' message in the log and every ten minutes I see that it restarts the background task.
When the app enters the background by pressing the power button, the 'staying alive' messages start showing up for ten minutes, after that it restarts the background task and start restarting it about every 50-100 miliseconds.
I would've been fine with this for now, even it eats battery, because I have time to work on updates and our users don't own the ipads, we do. The problem for me now is that the ximss library loses it's connection (it is session-based). I could restart the session in the library, but this means quite a bit of data transfer to fetch the contacts list and some users use 3g.
I can't edit the library's source, nor can I see it, so I don't know if it creates the sockets the right way.
What do I have to do to handle both situations correctly? I don't even understand why there is a difference.
You cannot re-extend background tasks like this; your app is likely to be terminated. If this is working, it's because you have the background voip mode enabled, not because you are restarting the background task.
Once you have set the voip plist entry, iOS will attempt to keep your app alive as long as possible and restart it if it does get terminated. From Implementing a VoIP App:
Including the voip value in the UIBackgroundModes key lets the system
know that it should allow the app to run in the background as needed
to manage its network sockets. An app with this key is also relaunched
in the background immediately after system boot to ensure that the
VoIP services are always available.
In addition to setting this key, if you need to periodically run code to keep your voip connection alive, you can use the setKeepAliveTimeout:handler: method on UIApplication.
See also Tips for Developing a VoIP App:
There are several requirements for implementing a VoIP app:
Add the UIBackgroundModes key to your app’s Info.plist file. Set the value of this key to an array that includes the voip string.
Configure one of the app’s sockets for VoIP usage.
Before moving to the background, call the setKeepAliveTimeout:handler: method to install a handler to be
executed periodically. Your app can use this handler to maintain its
service connection.
Configure your audio session to handle transitions to and from active use.
To ensure a better user experience on iPhone, use the Core Telephony framework to adjust your behavior in relation to cell-based
phone calls; see Core Telephony Framework Reference.
To ensure good performance for your VoIP app, use the System Configuration framework to detect network changes and allow your app
to sleep as much as possible.
Almost all of the documentation you need is on the Apple developer site.
Here is the situation:
I am picking a large video using imagepicker. Obviously the picker will take a bit of time to compress the video. So to ease user experience I have enabled background task for it.
Now here comes the issue:
If user choose a video and and tap the home button, application goes to background and continue compressing video for next 600 secs. And the background task expires. In the expiration handler I have stopped my background task.
Now if the user is resuming app after the background expiration
- (void)applicationDidBecomeActive:(UIApplication *)application
is not being invoked. Can anyone explain me why this happens?
When the background tasks expires, your app will really be closed! So it's not becoming active again, it's launching.
You should handle stuff in your expiration handler or/and when your background task ends successfully. Both situations, you need to set the background_task as invalidated.
If your app goes to background while converting the video, and then user open it again BEFORE the task end or the background task expires, then you should see the app calling applicationDidBecomeActive.
I assume you know it, but maybe you are missing the multitask properties in your Info.plist file, so your app isn't accepting background tasks the way you expect.
-(void) applicationDidBecomeActive:(UIApplication)application
This method Only called when the app's sate is changed from inactive state to active state.
Is it possible to know whether video picking finished? if it's possible then just store it. and when the user comes again to the app. just fire the functionality you required.
Could you try to add log statement to method applicationDidFinishLaunching? May be the app terminates or crashes before a user opens it.
Also, I think correct way is to save current parsing context when app receives signal like applicationDidFinishLaunching and when app starts resume parsing. Because a user can close the app manually.
I am programming an iPhone app, and I need to force it to exit due to certain user actions. After cleaning up memory the app allocated, what's the appropriate method to call to terminate the application?
On the iPhone there is no concept of quitting an app. The only action that should cause an app to quit is touching the Home button on the phone, and that's not something developers have access to.
According to Apple, your app should not terminate on its own. Since the user did not hit the Home button, any return to the Home screen gives the user the impression that your app crashed. This is confusing, non-standard behavior and should be avoided.
Have you tried exit(0)?
Alternatively, [[NSThread mainThread] exit], although I have not tried that it seems like the more appropriate solution.
exit(0) appears to a user as crashes, so show a confirmation message to user. After confirmation suspend(home button press programmatically) and wait 2 seconds while app is going background with animation then exit behind user's view
-(IBAction)doExit
{
//show confirmation message to user
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Confirmation"
message:#"Do you want to exit?"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"OK", nil];
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex != 0) // 0 == the cancel button
{
//home button press programmatically
UIApplication *app = [UIApplication sharedApplication];
[app performSelector:#selector(suspend)];
//wait 2 seconds while app is going background
[NSThread sleepForTimeInterval:2.0];
//exit app when app is in background
exit(0);
}
}
Check the Q&A here: https://developer.apple.com/library/content/qa/qa1561/_index.html
Q: How do I programmatically quit my iOS application?
There is no API provided for gracefully terminating an iOS application.
In iOS, the user presses the Home button to close applications. Should your application have conditions in which it cannot provide its intended function, the recommended approach is to display an alert for the user that indicates the nature of the problem and possible actions the user could take — turning on WiFi, enabling Location Services, etc. Allow the user to terminate the application at their own discretion.
WARNING: Do not call the exit function. Applications calling exit will appear to the user to have crashed, rather than performing a graceful termination and animating back to the Home screen.
Additionally, data may not be saved, because -applicationWillTerminate: and similar UIApplicationDelegate methods will not be invoked if you call exit.
If during development or testing it is necessary to terminate your application, the abort function, or assert macro is recommended
Its not really a way to quit the program, but a way to force people to quit.
UIAlertView *anAlert = [[UIAlertView alloc] initWithTitle:#"Hit Home Button to Exit" message:#"Tell em why they're quiting" delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
[anAlert show];
Go to your info.plist and check the key "Application does not run in background". This time when the user clicks the home button, the application exits completely.
Add UIApplicationExitsOnSuspend property on application-info.plist to true.
After some tests, I can say the following:
using the private interface : [UIApplication sharedApplication] will cause the app looking like it crashed, BUT it will call - (void)applicationWillTerminate:(UIApplication *)application before doing so;
using exit(0); will also terminate the application, but it will look "normal" (the springboard's icons appears like expected, with the zoom out effect), BUT it won't call the - (void)applicationWillTerminate:(UIApplication *)application delegate method.
My advice:
Manually call the - (void)applicationWillTerminate:(UIApplication *)application on the delegate.
Call exit(0);.
Your ApplicationDelegate gets notified of intentional quitting by the user:
- (void)applicationWillResignActive:(UIApplication *)application {
When I get this notification I just call
exit(0);
Which does all the work. And the best thing is, it is the useres intent to quit, which is why this should not be a problem calling it there.
On my Audio-App it was necessary to quit the app after people were syncing their device while the music was still playing. As soon as the syncing is complete I get a notification. But quitting the app right after that would actually look like a crash.
So instead I set a flag to REALLY quit the app on the next backgrounding action. Which is okay for refreshing the app after a sync.
My App has been rejected recently bc I've used an undocumented method. Literally:
"Unfortunately it cannot be added to the App Store because it is using a private API. Use of non-public APIs, which as outlined in the iPhone Developer Program License Agreement section 3.3.1 is prohibited:
"3.3.1 Applications may only use Documented APIs in the manner prescribed by Apple and must not use or call any private APIs."
The non-public API that is included in your application is terminateWithSuccess"
Apple say:
"Warning: Do not call the exit function. Applications calling exit will appear to the user to have crashed, rather than performing a graceful termination and animating back to the Home screen."
I think that this is a bad assumption. If the user tap a quit button and a message appears that say something like: "The application will now quit.", it doesn't appear to be crashed. Apple should provide a valid way to quit an application (not exit(0)).
You should not directly call the function exit(0) as it will quit the application immediately and will look like your app is crashed. So better to show users a confirmation alert and let them do this themselves.
Swift 4.2
func askForQuit(_ completion:#escaping (_ canQuit: Bool) -> Void) {
let alert = UIAlertController(title: "Confirmation!", message: "Do you want to quit the application", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Yes", style: UIAlertAction.Style.default, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
completion(true)
}))
alert.addAction(UIAlertAction(title: "No", style: UIAlertAction.Style.cancel, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
completion(false)
}))
self.present(alert, animated: true, completion: nil)
}
/// Will quit the application with animation
func quit() {
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
/// Sleep for a while to let the app goes in background
sleep(2)
exit(0)
}
Usage:
self.askForQuit { (canQuit) in
if canQuit {
self.quit()
}
}
This has gotten a good answer but decided to expand a bit:
You can't get your application accepted to AppStore without reading Apple's iOS Human Interface Guidelines well. (they retain the right to reject you for doing anything against them) The section "Don't Quit Programmatically" http://developer.apple.com/library/ios/#DOCUMENTATION/UserExperience/Conceptual/MobileHIG/UEBestPractices/UEBestPractices.html
is an exact guideline in how you should treat in this case.
If you ever have a problem with Apple platform you can't easily find a solution for, consult HIG. It's possible Apple simply doesn't want you to do it and they usually (I'm not Apple so I can't guarantee always) do say so in their documentation.
Hm, you may 'have to' quit the application if, say, your application requires an internet connection. You could display an alert and then do something like this:
if ([[UIApplication sharedApplication] respondsToSelector:#selector(terminate)]) {
[[UIApplication sharedApplication] performSelector:#selector(terminate)];
} else {
kill(getpid(), SIGINT);
}
We can not quit app using exit(0), abort() functions, as Apple strongly discourage the use of these functions. Though you can use this functions for development or testing purpose.
If during development or testing it is necessary to terminate your
application, the abort function, or assert macro is recommended
Please find this Apple Q&A thread to get more information.
As use of this function create impression like application is crashing. So i got some suggestion like we can display Alert with termination message to aware user about closing the app, due to unavailability of certain functionality.
But iOS Human Interface Guideline for Starting And Stopping App, suggesting that Never use Quit or Close button to terminate Application. Rather then that they are suggesting to display proper message to explain situation.
An iOS app never displays a Close or Quit option. People stop using an
app when they switch to another app, return to the Home screen, or put
their devices in sleep mode.
Never quit an iOS app programmatically. People tend to interpret this
as a crash. If something prevents your app from functioning as
intended, you need to tell users about the situation and explain what
they can do about it.
In addition to the above, good, answer I just wanted to add, think about cleaning up your memory.
After your application exits, the iPhone OS will automatically clean up anything your application left behind, so freeing all memory manually can just increase the amount of time it takes your application to exit.
- (IBAction)logOutButton:(id)sender
{
//show confirmation message to user
CustomAlert* alert = [[CustomAlert alloc] initWithTitle:#"Confirmation" message:#"Do you want to exit?" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil];
alert.style = AlertStyleWhite;
[alert setFontName:#"Helvetica" fontColor:[UIColor blackColor] fontShadowColor:[UIColor clearColor]];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex != 0) // 0 == the cancel button
{
//home button press programmatically
UIApplication *app = [UIApplication sharedApplication];
[app performSelector:#selector(suspend)];
//wait 2 seconds while app is going background
[NSThread sleepForTimeInterval:2.0];
//exit app when app is in background
NSLog(#"exit(0)");
exit(0);
}
}
I used the [[NSMutableArray new] addObject:nil] approach mentioned above to force-quit (crash) the app without making a tell-tale exit(0) function call.
Why? Because my app uses certificate pinning on all network API calls to prevent man-in-the-middle attacks. These include the initialization calls my financial app makes on startup.
If certificate authentication fails, all of my initialization calls error out and leave my app in an indeterminate state. Letting the user go home and then back into the app doesn't help, as unless the app has been purged by the OS it's still uninitialized and untrustworthy.
So, in this one case, we deemed it best to pop an alert informing the user that the app is operating in an insecure environment and then, when they hit "Close", force quit the app using the aforementioned method.
[[UIApplication sharedApplication] terminateWithSuccess];
It worked fine and automatically calls
- (void)applicationWillTerminateUIApplication *)application delegate.
to remove compile time warning add this code
#interface UIApplication(MyExtras)
- (void)terminateWithSuccess;
#end
The user should decide when an app exits.
I don't think it is a good user interaction when an app quits. Therefore there is no nice API for it, only the home button has one.
If there is an error: Implement it better or Notify the user.
If there have to be a restart: Implement it better of Notify the user.
It sounds dumb, but it's bad practice to exit the app without letting the user decide and not notifying him. And since there is a home button for the user interaction, Apple states, there should not be 2 things for the same function (exiting an app).
Exit an app other way than the home button is really non-iOS-esque approach.
I did this helper, though, that use no private stuff:
void crash()
{ [[NSMutableArray new] addObject:NSStringFromClass(nil)]; }
But still not meant for production in my case. It is for testing crash reportings, or to fast restart after a Core Data reset. Just made it safe not to be rejected if function left in the production code.
It may be appropriate to exit an app if it is a long lived app that also executes in the background, for example to get location updates (using the location updates background capability for that).
For example, let's say the user logs out of your location based app, and pushes the app to the background using the home button. In this case your app may keep running, but it could make sense to completely exit it. It would be good for the user (releases memory and other resources that don't need to be used), and good for app stability (i.e. making sure the app is periodically restarted when possible is a safety net against memory leaks and other low memory issues).
This could (though probably shouldn't, see below :-) be achieved with something like:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
if (/* logged out */) {
exit(0);
} else {
// normal handling.
}
}
Since the app would then exit out of the background it will not look wrong to the user, and will not resemble a crash, providing the user interface is restored the next time they run the app. In other words, to the user it would not look any different to a system initiated termination of the app when the app is in the background.
Still, it would be preferable to use a more standard approach to let the system know that the app can be terminated. For example in this case, by making sure the GPS is not in use by stopping requesting location updates, including turning off show current location on a map view if present. That way the system will take care of terminating the app a few minutes (i.e. [[UIApplication sharedApplication] backgroundTimeRemaining]) after the app enters the background. This would get all the same benefits without having to use code to terminate the app.
- (void)applicationDidEnterBackground:(UIApplication *)application
{
if (/* logged out */) {
// stop requesting location updates if not already done so
// tidy up as app will soon be terminated (run a background task using beginBackgroundTaskWithExpirationHandler if needed).
} else {
// normal handling.
}
}
And of course, using exit(0) would never be appropriate for the average production app that runs in the foreground, as per other answers that reference http://developer.apple.com/iphone/library/qa/qa2008/qa1561.html
Swift 4.2 (or older)
Library called Darvin can be used.
import Darwin
exit(0) // Here you go
NB: This is not recomanded in iOS applications.
Doing this will get you crash log.
In iPadOS 13 you can now close all scene sessions like this:
for session in UIApplication.shared.openSessions {
UIApplication.shared.requestSceneSessionDestruction(session, options: nil, errorHandler: nil)
}
This will call applicationWillTerminate(_ application: UIApplication) on your app delegate and terminate the app in the end.
But beware of two things:
This is certainly not meant to be used for closing all scenes. (see https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/multiple-windows/)
It compiles and runs fine on iOS 13 on an iPhone, but seems to do nothing.
More info about scenes in iOS/iPadOS 13: https://developer.apple.com/documentation/uikit/app_and_environment/scenes