When a foreground app gets backgrounded (e.g. Home button gets pressed), how can I change elements on the topmost view controller prior to when iOS takes a snapshot of it and starts the animation to show the next screen?
I ask because I'm writing an app requiring HIPAA compliance, and I am concerned that the snapshot that the OS takes in order to do this animation sometimes contains sensitive data which should not be visible even for a split second when the app gets foregrounded later.
I'm aware that view controllers have lifecycle methods such as viewWillDisappear which might be usable, but I have a lot of controllers and I'd rather just have something in my App Delegate to handle this (e.g. by adding an opaque full-screen UIImageView overlay) rather than having to write custom code for this in every last controller.
I tried putting overlay-generating code in applicationWillResignActive, and I've been digging with Apple's docs and Google, but it's not working. I suspect the screenshot gets taken before the app has a chance to update the screen.
Thanks!
Not sure about HIPAA's requirements about backgrounding and possibly leaving the user logged in for someone else to resume, but the safest sounds like it would be to add a key UIApplicationExitsOnSuspend with a boolean value of YES to info.plist.
That will prevent the app from backgrounding entirely, and restarts it (possibly triggering a login procedure) every time you go back to it.
Most (if not all) mobile banking applications I've tested do this for safety reasons.
I believe the answer is to not concern oneself with changing what's on the screen before the backgrounding animation begins, but to simply modify what's displayed on the screen once the app enters the background (i.e. inside of applicationDidEnterBackground: in your App Delegate.) This solved my problem.
My UIImageView overlay idea worked here, although I decided just to pop to the root view controller instead. Simpler that way. My root view doesn't have any sensitive info.
Here's what it looks like:
-(void)applicationDidEnterBackground:(UIApplication *)application {
UINavigationController *navigationController =
(UINavigationController *)self.window.rootViewController;
[navigationController popToRootViewControllerAnimated:NO];
...
}
Related
for the app I'm developing, I implemented a lock screen that allows the user to unlock the App by manual-pin or touch/Face-ID.
Everything is working OK during the normal use.
However, I need to show the lock screen when the app is resumed from background and even in the task switcher to avoid "peeking" at the content without having unlocked properly.
As recommended by Apple in this (old) article, I present the lock view controller in applicationDidEnterBackground:
func applicationDidEnterBackground(_ application: UIApplication) {
let lockVC = LoginViewController()
lockVC.loginType = LoginViewController.LoginType.resumeApp
if let topViewController = UIApplication.topViewController() {
topViewController.present(lockVC, animated: false, completion: nil)
}
}
where topViewControler is a useful extension to determnine the topmost view controller: Get top most UIViewController
.
The lockVC.loginType = ... is just to let the ViewController what type of login I need and customize its view a little bit
The results I get are a bit weird, both on simulator and real devices:
the task switcher preview for my app is completely black
when the app is resumed, the screen remains black as in preview and unresponsive. Only way to exit is to kill the app.
before obtaining the weird results above, I had to access all outlets as optional to avoid termination... that's fine for viewDidLoad stuff (I didn't expect the need for this when entering background, since the view had been loaded before - outlet wired) but the strange thing is that I had the same error for an IBAction in viewDidAppear (IBAction for touch-id button called automatically if touch/face-id is available - just a requirement).
I believe I'm missing something big here... but no other hints found.
No one of the ready-made lock screen solutions come with an example for the background/taskSwicth/resume case).
Note that the black/unresponsive screen seems to be same both if I use the mentioned extension for the topmost view controller to present or if I simply try to present it by
self.window?.rootViewController?.present(lockVC, animated: false)
(which I believe is wrong, but tried anyway)
Any help would be apreciated
I found a temporary workaround:
Display the lock screen when the app becomes active from background, as suggested by Dev_Tandel
Hide information in the task switcher by adding a blur effect on the current screen where the app is sent to background (applicationWillResignActive) and removing it when the app comes active again (applicationDidBecomeActive), as suggested here
As said, it's a temporary workaround that i wanted to share, but I don't like this 100%. It is required to show the lock screen in the task swtcher and Ive seen apps doing it (e.g. "oneSafe").
Still looking for help.
Solved, thanks to this post.
Apparently I was naive in trying to present a view controller with just its object instance.
If I use
let lockVC = topViewController.storyboard?.instantiateViewController(withIdentifier: "IDLoginViewController") as! LoginViewController
everything works, the lock screen is shown also in the task switcher, no need to blur!
My iOS app returns back to rootViewController when kept in background for longer time.
To demonstrate I have the below picture
I navigate all the way to ViewControllerC and keeps the app in background, when I returns to the app say after 30 mins, the app shows rootViewController i.e. ViewControllerA.
I want it to remain on ViewControllerC as it stays in Whatsapp. I know that my app (idle apps) will be removed from the memory when system falls short in memory, so when I open the app it will take me back to the initial View controller specified. But then why this doesn't happen with whatsapp?
Apple provides a solution to this situation: UIViewController, along with the App Delegate, has methods permitting you to save and restore state. When the app goes into the background, the current configuration (what view controller's view is showing) is saved. That way, even when the app quits, when it relaunches it can get back to that configuration before it appears to the user. Thus, coming back from background-and-quit looks just like coming back from mere backgrounding.
For full details, see Apple's documentation. This is a good place to start:
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html
What I'm looking for is a mechanism to keep my app from resetting to the root view controller every time the application gets backgrounded or goes inactive. There are many apps out there that operate this way, namely Instagram, eBay, etc.
My instincts told me initially to poke into the AppDelegate's applicationWillEnterForeground method, where I would try to present the viewcontroller I'm after, however when I instantiate the viewController, I can present it, but without the navigation controller that normally be there.
This makes me think that I need to save the "state" of the application (maybe the NavigationController's stack?) and then restore the stack somehow when it gets relaunched.
I have watched the execution timings of each event and notice that closing the application and relaunching it will start the application fresh. I assume that my NSUserDefaults are still in place and thus could be checked for a logged in user. This could help determine which view in the navigation controller to push to (either login or dashboard).
Any direction is greatly appreciated.
The most revealing answer in this post was the use of some storage (NSUserDefaults) in order to store persistent data across uses.
For my specific case, this was storing a key holding user info. Then when the application loads, the would-be first view comes up, but if that key is missing, will modally pull a login view in front of it.
To do this I would use NSUserDefaults to store the current view of the app and then switch to that view when your app finishes launching. See the answers to this question: Swift: How to store user preferences?
I have "Tinder" like swipping view that is located in a CardViewController. The card View Controller is accessed by moving through two other view controllers. i.e. Load App -> FirstViewController -> SecondViewController - > CardViewController.
When I am in the Card ViewController and I go into background mode, the app launches on the FirstViewController and on going to the cards, they are loaded from the first card in a stack of about 10?
Is there anyway to load the app from the last Card I had swipped and in the CardViewController without having to navigate from the FirstView Controller again?
I would really appreciate the help as it's horribly affecting some of my users.
An example of a Tinder like card view is shown!
The problem, from the sound of it, is not what happens when the app goes into the background — that would leave it in exactly the same state when it reactivates. The problem is what happens when the app goes into the background and quits. Your app is then relaunched from scratch, which is why you find yourself in the first view controller. What's upsetting you is the difference between the app's behavior in these two situations.
Apple provides a solution to this situation: UIViewController, along with the App Delegate, has methods permitting you to save and restore state. When the app goes into the background, the current configuration (what view controller's view is showing) is saved. That way, even when the app quits, when it relaunches it can get back to that configuration before it appears to the user. Thus, coming back from background-and-quit looks just like coming back from mere backgrounding.
For full details, see Apple's documentation. This is a good place to start:
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html
Long story short, I'm trying to change my iOS app's rootViewController on applicationWillEnterForeground:, like so:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
MyViewController *controller = [[MyViewController alloc] init];
self.window.rootViewController = controller;
}
However, when iOS performs the "zoom in" animation that is performed when an app is moved from the background to the foreground, it still shows the previous rootViewController's view. Then, as soon as the animation is complete, the app blasts the new rootViewController's view onto the screen.
One way to solve this is to simply move that code to - (void)applicationDidEnterBackground:, but the problem with this solution is that, in my app, there is no way to tell if a new rootViewController will be assigned until - (void)applicationWillEnterForeground:(UIApplication *)application (it is based on time passed since leaving the app).
How can I force the app to redraw before iOS performs the animation taking the app from the background to the foreground?
I believe this is not possible. The screen that iOS shows of your app when it comes into the foreground is actually a screenshot the system took when the app went into the background. There is no way to manipulate or replace that image at the time the app comes back into the foreground.
This behavior is partly documented in the Moving to the Background section of the iOS Application Programming Guide:
Apps can use their applicationDidEnterBackground: method to prepare for moving to the background state. When moving to the background, all apps should do the following:
Prepare to have their picture taken. When the applicationDidEnterBackground: method returns, the system takes a picture of your app’s user interface and uses the resulting image for transition animations. If any views in your interface contain sensitive information, you should hide or modify those views before the applicationDidEnterBackground: method returns.
Apple does not explicitly document that you cannot modify or replace this screenshot at a later time but neither do they say the opposite anywhere I know of.