I have a Xamarin Forms app. The iOS app presents a modal view controller like so:
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(viewController, true, null);
The current view goes into the background and the modal view is presented. When that modal view is dismissed, it goes away but the view that went in the background stays there. I'm not sure how to bring it back to the front and make it active.
FYI, the viewController in this case is a native iOS UIViewController inside a framework. When used inside a native iOS project in Xcode, the modal view dismisses properly and the old view returns to the foreground.
Any thoughts?
In iOS there is a new behaviour for modal view controller when being presented. Now it's not fullscreen by default and when I try to slide down, the app just dismiss the View Controller automatically.
How can I prevent this behaviour and get back to the old good fullscreen modal vc? I face this problem while using appodeal framework.
Code to show videoad
if Appodeal.isReadyForShow(with: .nonSkippableVideo){
Appodeal.showAd(.nonSkippableVideo, rootViewController: self)
}
Some background:
The app has a TabBarController. TabBarController has VC1.1 and VC2.1.
The app supports only portrait for iPhone and for 1 screen that we call VC1.2 (that I will mention later) it will show it in landscape mode.
But for iPad we support both portrait and landscape.
VC2.1 is a Controller which has NavigationController. This Screen 2.1 can push the new screen 2.2. Important note is that the screen 2.1 and 2.2 on iPad are presented together in a split view. But on iPhone we present it only separately in 2.1 and 2.2.
So how the crash happens.
The crash reproduces only with iPhone+ or X Max.
Open VC2.1 and press the button to push VC2.2.
Go back to VC1.1 and press button to present the screen VC1.2 that must be in landscape.
The app crashes with this info:
Warning: Attempt to present on whose view is not in the window hierarchy!
Unbalanced calls to begin/end appearance transitions for .
[Assert] Trying to dismiss the presentation controller while transitioning already. (<_UIFullscreenPresentationController>)
[Assert] transitionViewForCurrentTransition is not set, presentation controller was dismissed during the presentation? (<_UIFullscreenPresentationController)
So what happens is that on VC1.2 we are show the screen that we force to be in landscape mode.
And in VC2.2 (and VC2.1) since there is a SplitView (even though for iPhone it is not used) it somehow tries to rotate it and "show" (or pretend showing) that split view. And it crashes because it can't do it.
So we understand why this happens but can't understand if there is a way to fix it or is it an iOS issue maybe.
If it helps, in AppDelegate we have this method:
private func defaultInterfaceOrientations(for window: UIWindow?) -> UIInterfaceOrientationMask {
let isPhone: Bool
if let window = window {
isPhone = window.traitCollection.userInterfaceIdiom == .phone
} else {
isPhone = UIDevice.current.userInterfaceIdiom == .phone
}
return isPhone ? .portrait : .all
}
EDIT1:
There is one very interesting thing that happens in this flow and which may give an additional hint.
Steps:
Open VC2.1. Press button to push VC2.2. Press back button (pop).
Go to VC1.1. Open the landscape screen V1.2. It won't crash. Meaning it crashes only when VC2.2 is opened, but when VC2.1 is opened - it doesn't crash.
The interesting point: when you open the VC2 (tab bar item) you would expect it to be on VC2.1 since we went back (pop). But for some reason it will be on screen VC2.2. So somehow in the "back" it made a push of VC2.2.
I recently refactored an app to use a UINavigationController and pushViewController(...) to transition back and forth between the main UIViewControllers of my app. Previously I was using present(...) with no issues. The app is a music player and has the appropriate background mode.
The transition to view controller B from view controller A happens after a 5 second countdown and thus can happen in the background if the user hits their home button or turns the screen off.
The issue is that the push, and all relevant events that are supposed to kick off when the view did appear, do not fire while the app is in the background, and immediately fire as soon as I turn the display back on or bring the app into the foreground.
Is there any way to force the push to happen even if the app isn't visible, or a different method to push and display the view controller?
Thank for any insight!
let nav = self.parent as! UINavigationController
print("Attempting to .pushViewController...")
nav.pushViewController(sessionVC, animated: false)
print("After .pushViewController...")
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!