90

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.

shim
  • 9,289
  • 12
  • 69
  • 108
Nils Wasell
  • 987
  • 1
  • 8
  • 10

17 Answers17

138

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.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
Acey
  • 8,048
  • 4
  • 30
  • 46
  • 2
    If I put it in the viewDidAppear, then when I dismiss the presentedViewController, it will again call the viewDidAppear method and present the viewController again.I have a workaround of putting a conditional statement before presenting it, but is there any way instead of putting a conditional statement? – Puneet Nov 07 '14 at 17:42
  • 2
    I would say that trying to present another modal right when a screen appears is odd behavior to start with, so a state bool is hardly the worst solution. At the same time, think about the root reason you are needing to display the modal and see if you can access that information. – Acey Nov 07 '14 at 18:13
  • 2
    If you do it in viewWillAppear, you will have the situation where it's in the middle of presenting when you start your presentation. An alternate would be to put it in viewWillAppear but use the transitionCoordinator property new in iOS 8 to add the code in the completion of the transition. More info can be found here https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIViewControllerTransitionCoordinator_Protocol/index.html – Acey Feb 28 '15 at 04:04
  • I can't thank you enough. This just fixed my issue I've been trying to figure out the last several days. – Not Batman Jul 06 '23 at 23:33
36

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;
}
Darshan Kunjadiya
  • 3,323
  • 1
  • 29
  • 31
akr ios
  • 611
  • 1
  • 7
  • 9
36

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.

Jason
  • 1,587
  • 1
  • 19
  • 26
19

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)
  • 1
    Thanx @Jacob Davis for this! I used it to present an alertController cause i was getting the Warning: Attempt to present.... – Gary Mansted Dec 19 '17 at 23:55
  • This is very useful. some what am getting presenting controller But screen is not appearing . suddenly its get dismissed. Any other code needs to implement? please suggest – Seethalakshmi_user8943692 Oct 07 '20 at 07:44
14

for SWIFT

func topMostController() -> UIViewController {
    var topController: UIViewController = UIApplication.sharedApplication().keyWindow!.rootViewController!
    while (topController.presentedViewController != nil) {
        topController = topController.presentedViewController!
    }
    return topController
}
HDJEMAI
  • 9,436
  • 46
  • 67
  • 93
  • 1
    Thank you! This was the only thing that solved this error for me. I was trying to present a modal VC from a function triggered by a gesture recognizer, well after the view fully loaded (in Xcode 8 / iOS 10). Strangely, the `topController` returned by this function is the exact same VC as `self` in when trying to present directly using `self.presentViewController(myModalVC)`, but there is no view hierarchy error when I pass in the presenting VC using your function. – Natalia Dec 30 '16 at 00:01
14

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) 
}
Edward
  • 2,864
  • 2
  • 29
  • 39
7

Swift 4

func topMostController() -> UIViewController {
    var topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
    while (topController.presentedViewController != nil) {
        topController = topController.presentedViewController!
    }
    return topController
}
Allen Wixted
  • 179
  • 2
  • 12
5

Use of main thread to present and dismiss view controller worked for me.

DispatchQueue.main.async { self.present(viewController, animated: true, completion: nil) }
dinesh sharma
  • 577
  • 10
  • 20
3

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.

Bohdan Savych
  • 3,310
  • 4
  • 28
  • 47
2
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

Jason
  • 1,587
  • 1
  • 19
  • 26
2

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}
Hiren Panchal
  • 2,963
  • 1
  • 25
  • 21
2

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)
Bavafaali
  • 388
  • 1
  • 5
  • 14
1

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!
  }
}
1

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
    }
}
Grzegorz Krukowski
  • 18,081
  • 5
  • 50
  • 71
1

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.

Reinhard Männer
  • 14,022
  • 5
  • 54
  • 116
1

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)
}
Zgpeace
  • 3,927
  • 33
  • 31
0

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

Akhil Dad
  • 1,804
  • 22
  • 35