Dismiss and popViewController not working when presentingViewController is UITabbarController - ios

My scence
UITabbar ( UINavigation(page1) -push-> page2 -push-> page3 -present-> page4 )
From my feature I stay on page4 and want to dismiss it and pop to root ( page1 ) but when I get presentingViewController it return UITabbarController not my UINavigationBarController
My code now.
self.dismiss(animated: true) { [weak self] in
guard let topVc = self?.presentingViewController else { return }
if let destinationVC = topVc.navigationController?.viewControllers.filter({$0 is Page1ViewController}).first {
topVc.navigationController?.popToViewController(destinationVC, animated: true)
}
}
My code working before I add UITabbar to my flow.
Can someone help me or explain me about root cause ? Thank you for help.
Updated
Now I found a solution about this but I think it not the good way enough.
self.viewController.dismiss(animated: true) {
guard let topVc = Tools.getTopViewController(),
let tabBarVc = topVc as? MainTabBarController,
let nav = tabBarVc.selectedViewController as? BaseNavigationBarController else { return }
if let destinationVC = nav.viewControllers.filter({$0 is WalletViewController}).first {
nav.popToViewController(destinationVC, animated: true)
}
}

Related

Check to specific VC in AppDelegate

I'm trying to check specific VC if app is running in foreground. My root view controller class is SWRevealViewController. After that I have a TabBarController and under it there is NavigationController and ViewController under it.
My heirachy is,
SWRevealViewController --> TabBar Controller --> Navigation Controller --> MessageVC --> ChatVC
I want to check in app delegate if app is on ChatVC or not if running on foreground.I have tried this code,
let tabBar:UITabBarController = self.window?.rootViewController as! UITabBarController
let navInTab:UINavigationController = tabBar.viewControllers?[1] as! UINavigationController
let storyboard = UIStoryboard(name: "Dashboard", bundle: nil)
let destinationViewController = storyboard.instantiateViewController(withIdentifier: "ChatDetailViewController") as? ChatDetailViewController
if destinationViewController?.restorationIdentifier == "ChatDetailViewController"
{
print("Yes")
}
else
{
print("No")
}
But app crashes with this error,
Could not cast value of type 'SWRevealViewController' (0x100dc4b20) to 'UITabBarController' (0x211b289f0).
How i can check if app is on ChatVC or not?
Screenshot of storyboard :
I have an extension for it
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController, top.view.window != nil {
return topViewController(base: top)
} else if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
USAGE:
if UIApplication.topViewController is YourViewController {
// do smth
}
I never used SWRevealViewController but you can try this.
As you said in your first line "SWRevealViewController is the RooVC" and you are converting the SWRevealViewController to UITabBarController (check below line of your code). This is your crash reason.
let tabBar:UITabBarController = self.window?.rootViewController as! UITabBarController
Now you need to change, UITabBarController to SWRevealViewController
let rootVC = self.window?.rootViewController as! SWRevealViewController
Now get all ViewControllers
if let navController = rootVC.navigationController { // for safety check
for controller in navController.viewControllers {
if controller is ChatVC {
print("Chat VC is available")
break
}
}
}
For safe coding and keeping swift optional binding in mind, you can do code like below,
Updated answer
var haveChatVC = false
if let rootVC = self.window?.rootViewController as? SWRevealViewController,
let tabbar = rootVC.frontViewController as? UITabBarController {
if let requiredNC = tabbar.viewControllers?[1] as? UINavigationController {
for vc in requiredNC.viewControllers {
if vc is ChatVC {
// ...
haveChatVC = true
break
}
}
}
else {
print("Navigation controller not found.")
}
}
else {
print("Unable to get root controller or navigation controller")
}
if haveChatVC {
// do task here when chat vc available
}
else {
// do task here when chat vc not available
}
Note: This is only pseudo code.

Issue on dismissing a viewController to rootViewController

I am trying to dismiss a viewController to rootViewController while signOut. But the problem is that the viewController is not getting dismissed, It still remains in the same page itself.
Below I have mentioned the code that I have used.
let AppDel = UIApplication.shared.delegate as! AppDelegate
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let login = mainStoryboard.instantiateViewController(withIdentifier: "login")
let nav = UINavigationController(rootViewController: login)
AppDel.window!.rootViewController = nav
AppDel.window?.rootViewController?.dismiss(animated: true, completion: nil)
(AppDel.window?.rootViewController as? UINavigationController)?.popToRootViewController(animated: true)
login.navigationController?.setNavigationBarHidden(true, animated: false)
Thanks in advance.
Earlier, I have faced the same issue. I was fixed issue by performing all other operation after dismissing controller successfully.
Please refer below sample code. I am sure it will work for you.
AppDel.window?.rootViewController?.dismiss(animated: true, completion: {
(AppDel.window?.rootViewController as? UINavigationController)?.popToRootViewController(animated: true)
login.navigationController?.setNavigationBarHidden(true, animated: false)
})
in the App delegate type a function that takes the new view controller and set it as root. than dismisses the old one.
func updateRootViewController(with viewController: UIViewController) {
guard let oldViewController = self.window?.rootViewController else { return }
UIView.transition(from: oldViewController.view, to: viewController.view, duration: 0.3, options: [.transitionCrossDissolve, .allowAnimatedContent]) { _ in
self.window!.rootViewController = viewController
self.window!.makeKeyAndVisible()
oldViewController.dismiss(animated: false) {
oldViewController.view.removeFromSuperview()
}
}
}
Why have you dismissed your navigation controller before calling popToRootViewController ?
AppDel.window?.rootViewController?.dismiss(animated: true, completion:
nil)
Check if you are calling this from the main thread.
Add your code inside this block:
DispatchQueue.main.async {
// TODO: Your code
}

iOS Swift3 check nil value for ViewController Object

let viewControllers: [UIViewController] = self.navigationController!.viewControllers
for VC in viewControllers {
if (VC.isKind(of: HomeViewController.self)) {
bScreen = true
self.navigationController?.popToViewController(VC, animated: true)
}
}
if bScreen == false {
let homeVC = HomeViewController()
self.navigationController?.pushViewController(homeVC, animated: false)
}
I loop through navigation controller array to move to HomeViewController.above code is working fine.some times i am getting crash as “fatal error: unexpectedly found nil while unwrapping an Optional value”.I know the cause for this crash.Please help me how to check nil value for view controller object.any help will be appreciated.thanks in advance
-- Swift 3 --
for vc in (self.navigationController?.viewControllers)! {
if vc is HomeViewController {
_ = self.navigationController?.popToViewController(vc, animated: true)
}
}
Use this code. this is helpful for you.
let viewControllers: [UIViewController] = self.navigationController!.viewControllers
for VC in viewControllers {
if (VC.isKind(of: HomeViewController.self)) {
bScreen = true
self.navigationController?.popToViewController(VC, animated: true)
break;
}
}
if bScreen == false
{
let homeVC = HomeViewController()
self.navigationController?.pushViewController(homeVC, animated: false)
}
let getCurrentVCIndex = self.navigationController?.viewControllers.indexOf({ (viewController) -> Bool in
if let _ = viewController as? HomeViewController {
return true
}
return false
})
if getCurrentVCIndex
{
let HomeVC = self.navigationController?.viewControllers[getCurrentVCIndex!] as! HomeViewController
self.navigationController?.popToViewController(HomeVC, animated: true)
}
else
{
// use push
}
or use like
if let HomeVC = self.navigationController?.viewControllers.filter({$0 is HomeViewController}).first
{
self.navigationController?.popToViewController(HomeVC!, animated: true)
}else
{
// use push
}
Never use directly ! until you are damn sure that it will not be nil. Replace your code as below. You can use if let or guard let to unwrap optionals.
if let viewControllers: [UIViewController] = self.navigationController?.viewControllers {
for VC in viewControllers {
if (VC.isKind(of: ViewController.self)) {
bScreen = true
self.navigationController?.popToViewController(VC, animated: true)
}
}
if bScreen == false
{
let homeVC = ViewController()
self.navigationController?.pushViewController(homeVC, animated: false)
}
}
else {
// IF VC is nil
}
This is better to use to if let / guard for an optional value to avoid crashing.
if let viewControllers: [UIViewController] = self.navigationController.viewControllers{
for VC in viewControllers {
if (VC.isKind(of: HomeViewController.self)) {
bScreen = true
self.navigationController?.popToViewController(VC, animated: true)
}
}
if bScreen == false
{
let homeVC = HomeViewController()
self.navigationController?.pushViewController(homeVC, animated: false)
}
}
Based on your code, In the loop, if the navigation stack contains the respective view controller will be popped to the respective page. But the thing is if the same view controller is present two times, will lead to execute the loop for the same time. This may cause crash. So Add a break after the poptoviewcontroller will avoid this issue. Please check the below code, will help you.
if (VC.isKind(of: HomeViewController.self)) {
bScreen = true
self.navigationController?.popToViewController(VC, animated: true)
break
}

Show two ViewController from AppDelegate

When APP is Launching - start SigninView - it's Okey. Next if success - I need showTripController(). Function work but nothing show? What's a problem?
func showSigninView() {
let controller = self.window?.rootViewController!.storyboard?.instantiateViewControllerWithIdentifier("DRVAuthorizationViewController")
self.window?.rootViewController!.presentViewController(controller!, animated: true, completion: nil)
}
func showTripController() {
let cv = self.window?.rootViewController!.storyboard?.instantiateViewControllerWithIdentifier("DRVTripTableViewController")
let nc = UINavigationController()
self.window?.rootViewController!.presentViewController(nc, animated:true, completion: nil)
nc.pushViewController(cv!, animated: true);
}
First of all you must add this before you use window :
self.window.makeKeyAndVisible()
Another thing to keep in mind is:
Sometimes keyWindow may have been replaced by window with nil rootViewController (showing UIAlertViews, UIActionSheets on iPhone, etc), in that case you should use UIView's window property.
So, instead of using rootViewController, use the top one presented by it:
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
if let topController = UIApplication.topViewController() {
topController.presentViewController(vc, animated: true, completion: nil)
}
Replace last 3 lines of showTripController as below:
let nc = UINavigationController(rootViewController: cv));
self.window!.rootViewController = nc

Warning: Attempt to present * on * whose view is not in the window hierarchy - swift

I'm trying to present a ViewController if there is any saved data in the data model. But I get the following error:
Warning: Attempt to present * on *whose view is not in the window hierarchy"
Relevant code:
override func viewDidLoad() {
super.viewDidLoad()
loginButton.backgroundColor = UIColor.orangeColor()
var request = NSFetchRequest(entityName: "UserData")
request.returnsObjectsAsFaults = false
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var results:NSArray = context.executeFetchRequest(request, error: nil)!
if(results.count <= 0){
print("Inga resultat")
} else {
print("SWITCH VIEW PLOX")
let internVC = self.storyboard?.instantiateViewControllerWithIdentifier("internVC") as internViewController
self.presentViewController(internVC, animated: true, completion: nil)
}
}
I've tried different solutions found using Google without success.
At this point in your code the view controller's view has only been created but not added to any view hierarchy. If you want to present from that view controller as soon as possible you should do it in viewDidAppear to be safest.
In objective c:
This solved my problem when presenting viewcontroller on top of mpmovieplayer
- (UIViewController*) topMostController
{
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
return topController;
}
Swift 3
I had this keep coming up as a newbie and found that present loads modal views that can be dismissed but switching to root controller is best if you don't need to show a modal.
I was using this
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard?.instantiateViewController(withIdentifier: "MainAppStoryboard") as! TabbarController
present(vc, animated: false, completion: nil)
Using this instead with my tabController:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let view = storyboard.instantiateViewController(withIdentifier: "MainAppStoryboard") as UIViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
//show window
appDelegate.window?.rootViewController = view
Just adjust to a view controller if you need to switch between multiple storyboard screens.
Swift 3.
Call this function to get the topmost view controller, then have that view controller present.
func topMostController() -> UIViewController {
var topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
while (topController.presentedViewController != nil) {
topController = topController.presentedViewController!
}
return topController
}
Usage:
let topVC = topMostController()
let vcToPresent = self.storyboard!.instantiateViewController(withIdentifier: "YourVCStoryboardID") as! YourViewController
topVC.present(vcToPresent, animated: true, completion: nil)
for SWIFT
func topMostController() -> UIViewController {
var topController: UIViewController = UIApplication.sharedApplication().keyWindow!.rootViewController!
while (topController.presentedViewController != nil) {
topController = topController.presentedViewController!
}
return topController
}
You just need to perform a selector with a delay - (0 seconds works).
override func viewDidLoad() {
super.viewDidLoad()
perform(#selector(presentExampleController), with: nil, afterDelay: 0)
}
#objc private func presentExampleController() {
let exampleStoryboard = UIStoryboard(named: "example", bundle: nil)
let exampleVC = storyboard.instantiateViewController(withIdentifier: "ExampleVC") as! ExampleVC
present(exampleVC, animated: true)
}
Swift 4
func topMostController() -> UIViewController {
var topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
while (topController.presentedViewController != nil) {
topController = topController.presentedViewController!
}
return topController
}
Use of main thread to present and dismiss view controller worked for me.
DispatchQueue.main.async { self.present(viewController, animated: true, completion: nil) }
I was getting this error while was presenting controller after the user opens the deeplink.
I know this isn't the best solution, but if you are in short time frame here is a quick fix - just wrap your code in asyncAfter:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.7, execute: { [weak self] in
navigationController.present(signInCoordinator.baseController, animated: animated, completion: completion)
})
It will give time for your presenting controller to call viewDidAppear.
For swift 3.0 and above
public static func getTopViewController() -> UIViewController?{
if var topController = UIApplication.shared.keyWindow?.rootViewController
{
while (topController.presentedViewController != nil)
{
topController = topController.presentedViewController!
}
return topController
}
return nil}
let storyboard = UIStoryboard(name: "test", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "teststoryboard") as UIViewController
UIApplication.shared.keyWindow?.rootViewController?.present(vc, animated: true, completion: nil)
This seemed to work to make sure it's the top most view.
I was getting an error
Warning: Attempt to present myapp.testController: 0x7fdd01703990 on myapp.testController: 0x7fdd01703690 whose view is not in the window hierarchy!
Hope this helps others with swift 3
I have tried so many approches! the only useful thing is:
if var topController = UIApplication.shared.keyWindow?.rootViewController
{
while (topController.presentedViewController != nil)
{
topController = topController.presentedViewController!
}
}
All implementation for topViewController here are not fully supporting cases when you have UINavigationController or UITabBarController, for those two you need a bit different handling:
For UITabBarController and UINavigationController you need a different implementation.
Here is code I'm using to get topMostViewController:
protocol TopUIViewController {
func topUIViewController() -> UIViewController?
}
extension UIWindow : TopUIViewController {
func topUIViewController() -> UIViewController? {
if let rootViewController = self.rootViewController {
return self.recursiveTopUIViewController(from: rootViewController)
}
return nil
}
private func recursiveTopUIViewController(from: UIViewController?) -> UIViewController? {
if let topVC = from?.topUIViewController() { return recursiveTopUIViewController(from: topVC) ?? from }
return from
}
}
extension UIViewController : TopUIViewController {
#objc open func topUIViewController() -> UIViewController? {
return self.presentedViewController
}
}
extension UINavigationController {
override open func topUIViewController() -> UIViewController? {
return self.visibleViewController
}
}
extension UITabBarController {
override open func topUIViewController() -> UIViewController? {
return self.selectedViewController ?? presentedViewController
}
}
The previous answers relate to the situation where the view controller that should present a view 1) has not been added yet to the view hierarchy, or 2) is not the top view controller.
Another possibility is that an alert should be presented while another alert is already presented, and not yet dismissed.
Swift Method, and supply a demo.
func topMostController() -> UIViewController {
var topController: UIViewController = UIApplication.sharedApplication().keyWindow!.rootViewController!
while (topController.presentedViewController != nil) {
topController = topController.presentedViewController!
}
return topController
}
func demo() {
let vc = ViewController()
let nav = UINavigationController.init(rootViewController: vc)
topMostController().present(nav, animated: true, completion: nil)
}
Swift 5.1:
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
let mainViewController = storyboard.instantiateViewController(withIdentifier: "ID")
let appDeleg = UIApplication.shared.delegate as! AppDelegate
let root = appDeleg.window?.rootViewController as! UINavigationController
root.pushViewController(mainViewController, animated: true)
Rather than finding top view controller, one can use
viewController.modalPresentationStyle = UIModalPresentationStyle.currentContext
Where viewController is the controller which you want to present
This is useful when there are different kinds of views in hierarchy like TabBar, NavBar, though others seems to be correct but more sort of hackish
The other presentation style can be found on apple doc

Resources