Manually invoke state preservation in iOS 6 and onwards - ios

Some background
I'm working on an iOS app where we want the state of the application to be preserved.
Before this app is out, iOS 7 is likely to have been released or is soon to be released and the majority seems to have moved away from iOS 5. We have therefore decided to develop for iOS 6 an onwards.
In iOS 6 there are some really nice functionality for preserving state. Just give all views in the storyboard unique IDs and implement these two functions in the "AppDelegate":
- (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder;
- (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder;
iOS will then "automagically" preserve the navigation history of the app. The methods:
- (void)encodeRestorableStateWithCoder:(NSCoder*)coder;
- (void)decodeRestorableStateWithCoder:(NSCoder*)coder;
can then be used to store and retrieve data.
No problems there it works without issues. But, the methods that save the state are only triggered when the app entered the background.
Let's say we have one NavigationController with four ViewControllers: A, B, C and D. The user navigates from A to B, in B he switches over to Safari to google something. The application state is saved in B. The user then switches back to the app and navigates on to C and then to D. In D the app unfortunately encounters an exception and goes down. When the user restarts the app, iOS will try to restore the saved state. This state however, was saved in B. Which means that when the app launches, it doesn't start from the beginning, not where the user left it (D) and not even the previous view (C) but in B.
A possible solution
The above scenario could be avoided if the app saved its state at every new view. However there aren't (as far as I know) any public methods to trigger the state preservation process.
I have examined the call stack while debugging and found out that iOS calls the following method on the UIApplication object in iOS 6:
_saveApplicationPreservationState:
and the following method in iOS 7:
_saveApplicationPreservationState:viewController:sessionIdentifier:beginHandler:completionHandler:
There also seems to be another method that calls one of the above depending on the iOS version:
_saveApplicationPreservationStateIfSupported
By invoking this method like this:
if ([[UIApplication sharedApplication] respondsToSelector:#selector(_saveApplicationPreservationStateIfSupported)])
[[UIApplication sharedApplication] performSelector:#selector(_saveApplicationPreservationStateIfSupported)];
I can see that the expected methods are called.
The actual question
If I would go with the above solution could that get my app rejected from the App Store? I mean technically it's not a private method, it's just not exposed. By wrapping the call in "respondsToSelector" the app won't crash if the APIs are changed, it just won't save the state as often. But if it could get the app rejected it's not an option.
Or is there any other way to manually invoke the state preservation process other than the one described above? I would be nice to be able to use the built-in functionality rather than building a custom solution that saves the state to NSUserDefaults.

Event if the question is already 2 years old, i'll try my luck. Btw op, i guess, you'll have most likely solved it by now.
You're gonna for sure get rejected. They are scanning the sourcecode for methods you are calling. I had this several times, back when i was using the UDID methods. Hope it helped.

Related

IOS expo push notifications in app killed state

To give more clarity on the issue, i am developing this for IOS using expo push notifications and react-navigation v6 along with expo sdk44 in my current project.
I am having an issue when the user interacts with the notification while the app is in a killed state (the notificatiosn arrives succesfully with all of the data), what im having trouble with is, i want to navigate the user to a specific screen. The problem is because my navigationRef is null. Here i need to mention i did not create a seperate RootNavigator. Like it is shown here https://reactnavigation.org/docs/navigating-without-navigation-prop/ .
What would be the "proper" way of handling this? So far i've tried putting all of the relevant push notification listener code inside NavigatonContainer's onReady callback ( this worked).
I also tried making a seperate useEffect and changing the state of a isReady variable and setting its new value in <NavigationContainer ref={navigationRef} onReady={() => {setNavigatorReady(true)}}> to force a rerender and thus running the code inside useEffect again. (this approach did not work)
Just to sum up my current problem. When the user interacts with a notification while the app is in a killed state i want them to be navigated to a specific screen.
Thanks in advance.
I too am using react-navigation 6.x and expo. To tackle this problem I pretty much followed this section on expo's docs: https://docs.expo.dev/versions/latest/sdk/notifications/#addnotificationresponsereceivedlistenerlistener-event-notificationresponse--void-void.
It shows you how to implement addNotificationResponseReceivedListener which is called whenever a user interacts with a notification. It works in all situations, even when the app is killed, which is the specific situation you are interested in.
The docs also show you how to integrate this listener with react-navigation. Link: https://docs.expo.dev/versions/latest/sdk/notifications/#handling-push-notifications-with-react-navigation

Open your own application via URL schemes [duplicate]

I tested:
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
which is for putting app on background and it works.
How do I put app back on foreground?
I tried:
UIControl().sendAction(#selector(URLSessionTask.resume), to: UIApplication.shared, for: nil)
But eventually it crashes...
Thank you
Update:
Since you've indicated that you're looking for any technical solution, even those not compatible with the App Store or Apple's terms, this should be possible using the Private API LSApplicationWorkspace: openApplicationWithBundleID. Try something like this:
Create a .h file and set up an interface to the LSApplicationWorkspace class and list the required method. You will need to #import "PrivateHeaders.h" in your bridging header.
//
// PrivateHeaders.h
//
#ifndef PrivateHeaders_h
#define PrivateHeaders_h
#interface LSApplicationWorkspace : NSObject
- (bool)openApplicationWithBundleID:(id)arg1;
#end
#endif /* PrivateHeaders_h */
You should then be able to call this function and pass in the Bundle Identifier of your app as an string.
//
// SomeClass.swift
//
import MobileCoreServices
let workspace = LSApplicationWorkspace()
/**
Launch an App given its bundle identifier
- parameter bundleIdentifier: The bundle identifier of the app to launch
- returns: True if app is launched, otherwise false
*/
func openApp(withBundleIdentifier bundleIdentifier: String) -> Bool {
// Call the Private API LSApplicationWorkspace method
return workspace.openApplication(withBundleID: bundleIdentifier)
}
Original:
What you are doing is likely a violation of the iOS Human Interface Guidelines (although the "Don’t Quit Programmatically" is no longer specifically defined), so as the comments have said, it is not suited to the App Store. Regardless, once your app is suspended in this way, I don't expect that there is a way to resume it programmatically, unless you can hook into a Background Operation to run URLSessionTask.resume, but I have not tested it and am unsure whether it can work.
Apps can be launched (and hence brought into the foreground) programmatically from another app or today extension by using a Custom URL Scheme, or via a Push Notification. It isn't possible to launch the app from the Background Operation via a URL Scheme, since it is part of the UIKit framework, which must be run in the main thread.
In summary, I think your best option is to try to use a Notification. This just means that the user will need to click on the notification to bring your app back into the foreground.
Closing/opening the app should be done explicitly by the user. Any other way of closing or opening the app is not supported by Apple and will be rejected when uploaded to app store. iOS Human Interface Guideline states:
Don’t Quit Programmatically
Never quit an iOS application
programmatically because people tend to interpret this as a crash.
However, if external circumstances prevent your application from
functioning as intended, you need to tell your users about the
situation and explain what they can do about it. Depending on how
severe the application malfunction is, you have two choices.
*Display
an attractive screen that describes the problem and suggests a
correction. A screen provides feedback that reassures usersthat
there’s nothing wrong with your application. It puts usersin control,
letting them decide whether they want to take corrective action and
continue using your application or press the Home button and open a
different application
*If only some of your application's features are
not working, display either a screen or an alert when people activate
the feature. Display the alert only when people try to accessthe
feature that isn’t functioning
Just as a follow up to Jordan's excellent answer I want to give an explanation for why your code works in the first place and why that alone will get your app rejected, even without any functionality to make it active again and bring it to the foreground.
As maddy pointed out in a comment, you're basically calling a method from UIApplication's private API. This works due to the Objective-C runtime's dynamic linking. You might wonder "But I am using Swift, what does that have to do with Objective-C?" The answer lies in #selector mechanism. A Selector is basically just a symbol that the Objective-C runtime looks up in a table to get a method it invokes (for you). This is why it's technically not correct to say you "call a method" when you do something like myObjectInstance.someMethod(). The correct way to phrase that would be to "send a message" to the object, because that's what is happening in the runtime. The target-action mechanism is build around that. The sendAction(_: Selector?, to: Any?) method does the same thing. So in effect your code does the following:
Get the symbol that corresponds to URLSessionTask's suspend() method.
Tell the shared instance of UIApplication to invoke the method that it has for that symbol.
Now usually that would result in a crash with the typical "unknown selector sent to instance..." error message. But here, by sure coincidence UIApplication also has a method for that instance (or rather, the runtime also has one of its methods listed in its table for that symbol). You kind of "found" a method that is not declared in its public header. You successfully circumvented a compile-time check for this and invoke a method that is part of a private API. This is explicitly forbidden in the Apple Developer Program License Agreement
Besides all that, I would strongly advise against trying to design an app that way in the first place. As maddy pointed out it's also likely considered to violate the HIGs. Even if you're not trying to do anything malicious and properly explain the feature in your app's description, that won't make Apple let it slide (I assume). Personally, as a user, I'd also find it annoying if the app did something the system already has a specific mechanic for in a different manner, at least in terms of app's coming to background and foreground.
I don't think it can be done without user interaction
The option is you can generate a push notification to tell the user to bring the application to foreground
When the operating system delivers push notification and the target application is not running in the foreground, it presents the notification.
If there is a notification alert and the user taps or clicks the action button (or moves the action slider), the application launches and calls a method to pass in the local-notification object or remote-notification payload.

Put app on foreground programmatically on Swift

I tested:
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
which is for putting app on background and it works.
How do I put app back on foreground?
I tried:
UIControl().sendAction(#selector(URLSessionTask.resume), to: UIApplication.shared, for: nil)
But eventually it crashes...
Thank you
Update:
Since you've indicated that you're looking for any technical solution, even those not compatible with the App Store or Apple's terms, this should be possible using the Private API LSApplicationWorkspace: openApplicationWithBundleID. Try something like this:
Create a .h file and set up an interface to the LSApplicationWorkspace class and list the required method. You will need to #import "PrivateHeaders.h" in your bridging header.
//
// PrivateHeaders.h
//
#ifndef PrivateHeaders_h
#define PrivateHeaders_h
#interface LSApplicationWorkspace : NSObject
- (bool)openApplicationWithBundleID:(id)arg1;
#end
#endif /* PrivateHeaders_h */
You should then be able to call this function and pass in the Bundle Identifier of your app as an string.
//
// SomeClass.swift
//
import MobileCoreServices
let workspace = LSApplicationWorkspace()
/**
Launch an App given its bundle identifier
- parameter bundleIdentifier: The bundle identifier of the app to launch
- returns: True if app is launched, otherwise false
*/
func openApp(withBundleIdentifier bundleIdentifier: String) -> Bool {
// Call the Private API LSApplicationWorkspace method
return workspace.openApplication(withBundleID: bundleIdentifier)
}
Original:
What you are doing is likely a violation of the iOS Human Interface Guidelines (although the "Don’t Quit Programmatically" is no longer specifically defined), so as the comments have said, it is not suited to the App Store. Regardless, once your app is suspended in this way, I don't expect that there is a way to resume it programmatically, unless you can hook into a Background Operation to run URLSessionTask.resume, but I have not tested it and am unsure whether it can work.
Apps can be launched (and hence brought into the foreground) programmatically from another app or today extension by using a Custom URL Scheme, or via a Push Notification. It isn't possible to launch the app from the Background Operation via a URL Scheme, since it is part of the UIKit framework, which must be run in the main thread.
In summary, I think your best option is to try to use a Notification. This just means that the user will need to click on the notification to bring your app back into the foreground.
Closing/opening the app should be done explicitly by the user. Any other way of closing or opening the app is not supported by Apple and will be rejected when uploaded to app store. iOS Human Interface Guideline states:
Don’t Quit Programmatically
Never quit an iOS application
programmatically because people tend to interpret this as a crash.
However, if external circumstances prevent your application from
functioning as intended, you need to tell your users about the
situation and explain what they can do about it. Depending on how
severe the application malfunction is, you have two choices.
*Display
an attractive screen that describes the problem and suggests a
correction. A screen provides feedback that reassures usersthat
there’s nothing wrong with your application. It puts usersin control,
letting them decide whether they want to take corrective action and
continue using your application or press the Home button and open a
different application
*If only some of your application's features are
not working, display either a screen or an alert when people activate
the feature. Display the alert only when people try to accessthe
feature that isn’t functioning
Just as a follow up to Jordan's excellent answer I want to give an explanation for why your code works in the first place and why that alone will get your app rejected, even without any functionality to make it active again and bring it to the foreground.
As maddy pointed out in a comment, you're basically calling a method from UIApplication's private API. This works due to the Objective-C runtime's dynamic linking. You might wonder "But I am using Swift, what does that have to do with Objective-C?" The answer lies in #selector mechanism. A Selector is basically just a symbol that the Objective-C runtime looks up in a table to get a method it invokes (for you). This is why it's technically not correct to say you "call a method" when you do something like myObjectInstance.someMethod(). The correct way to phrase that would be to "send a message" to the object, because that's what is happening in the runtime. The target-action mechanism is build around that. The sendAction(_: Selector?, to: Any?) method does the same thing. So in effect your code does the following:
Get the symbol that corresponds to URLSessionTask's suspend() method.
Tell the shared instance of UIApplication to invoke the method that it has for that symbol.
Now usually that would result in a crash with the typical "unknown selector sent to instance..." error message. But here, by sure coincidence UIApplication also has a method for that instance (or rather, the runtime also has one of its methods listed in its table for that symbol). You kind of "found" a method that is not declared in its public header. You successfully circumvented a compile-time check for this and invoke a method that is part of a private API. This is explicitly forbidden in the Apple Developer Program License Agreement
Besides all that, I would strongly advise against trying to design an app that way in the first place. As maddy pointed out it's also likely considered to violate the HIGs. Even if you're not trying to do anything malicious and properly explain the feature in your app's description, that won't make Apple let it slide (I assume). Personally, as a user, I'd also find it annoying if the app did something the system already has a specific mechanic for in a different manner, at least in terms of app's coming to background and foreground.
I don't think it can be done without user interaction
The option is you can generate a push notification to tell the user to bring the application to foreground
When the operating system delivers push notification and the target application is not running in the foreground, it presents the notification.
If there is a notification alert and the user taps or clicks the action button (or moves the action slider), the application launches and calls a method to pass in the local-notification object or remote-notification payload.

How to check application state under swift UI Test

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.

Exit an application or Go to Dash board(main page) programmatically - IOS

I want to exit my application programatically, I googled, some people suggesting to use exit(1), but apple is not supporting that I guess. If it is the case, How do I exit my application programatically. Any helps appreciated.
exit(0); will work but don't use it
You shouldn't force close an app as the standard way to terminate an application is to press the home button (or use the multitasking bar)
Don’t Quit Programmatically
Never quit an iOS application programmatically because people tend to
interpret this as a crash. However, if external circumstances prevent
your application from functioning as intended, you need to tell your
users about the situation and explain what they can do about it.
Depending on how severe the application malfunction is, you have two
choices.
Display an attractive screen that describes the problem and suggests a
correction. A screen provides feedback that reassures users that
there’s nothing wrong with your application. It puts users in control,
letting them decide whether they want to take corrective action and
continue using your application or press the Home button and open a
different application
If only some of your application's features are not working, display
either a screen or an alert when people activate the feature. Display
the alert only when people try to access the feature that isn’t
functioning.
Source
I believe u are not reading the comment properly thus posting the answer for ur question here:
"Simply Don't do that. as apple does not allow application to crash like that."
look at here. How do I exit my iOS app gracefully after handling a Local Notification and here Exit application in iOS 4.0 there are fare discussion over here.
After the release of iOS4, multitasking(new feature) was added by APPLE. This feature enabled the users to keep the app into suspended state in the background if in between he has to do some other activity(e.g. picking up phone call). So Apple considers your app should be maintained in the background until the user deletes the application from the background. And after this if you want to exit use exit(0);, using this would further lead to rejection from AppStore
Here's a wrong way to accomplished exit function in your app. This is coming to mind when I read your question, never applied anywhere, so be careful if you'll gonna implement this!
- (void) exitApp
{
NSArray *array = [[[NSArray alloc] init] autorelease];
NSLog(#"%#",[array objectAtIndex:10]); //will crash here, looks like exit.
}
P.S. You can put this code inside your UIAlertView asking exit confirmation like Do you really want to exit?. In YES button pressed you can call [self exitApp]; User think that he'll exit from the app.

Resources