669

Just started using Xcode 4.5 and I got this error in the console:

Warning: Attempt to present < finishViewController: 0x1e56e0a0 > on < ViewController: 0x1ec3e000> whose view is not in the window hierarchy!

The view is still being presented and everything in the app is working fine. Is this something new in iOS 6?

This is the code I'm using to change between views:

UIStoryboard *storyboard = self.storyboard;
finishViewController *finished = 
[storyboard instantiateViewControllerWithIdentifier:@"finishViewController"];

[self presentViewController:finished animated:NO completion:NULL];
animaonline
  • 3,715
  • 5
  • 30
  • 57
Kyle Goslan
  • 10,748
  • 7
  • 26
  • 41
  • 3
    I'm having the exact same issue, except trying to call `presentViewController:animated:completion` on a nav controller. Are you doing this in the app delegate? – tarnfeld Aug 08 '12 at 20:43
  • No I am doing it from one view controller to another. Have you found any solutions? – Kyle Goslan Aug 08 '12 at 22:01
  • Same issue on a part of code that always worked prior to use Xcode 4.5, I'm presenting a UINavigationController, but again this always worked before. – Emanuele Fumagalli Sep 20 '12 at 14:52
  • I have the same problem, not solved. Doing it from the app delegate, and the rootviewcontroller calling "presentViewController" beeing a UITabBarController. – darksider Sep 22 '12 at 23:48
  • Piling on here, I have the same issue trying to present a modal view controller on iOS 6. Not an issue on iOS 6. :-( – theory Sep 24 '12 at 04:57
  • 3
    also, if calling this method before calling makeKeyAndVisible, move it after that – mike_haney Dec 18 '12 at 06:25
  • http://stackoverflow.com/a/24061440/884674 – jeet.chanchawat Dec 31 '14 at 10:48
  • This warning also occurs when self is not the top most view controller in the window hierarchy. – soumil Mar 14 '19 at 12:59
  • In my case, I had two perfromSegue which caused this error. Thanks to this discussion which helped me identify. Thanks. – Amit Khetan Jan 10 '21 at 13:06

38 Answers38

1408

Where are you calling this method from? I had an issue where I was attempting to present a modal view controller within the viewDidLoad method. The solution for me was to move this call to the viewDidAppear: method.

My presumption is that the view controller's view is not in the window's view hierarchy at the point that it has been loaded (when the viewDidLoad message is sent), but it is in the window hierarchy after it has been presented (when the viewDidAppear: message is sent).


Caution

If you do make a call to presentViewController:animated:completion: in the viewDidAppear: you may run into an issue whereby the modal view controller is always being presented whenever the view controller's view appears (which makes sense!) and so the modal view controller being presented will never go away...

Maybe this isn't the best place to present the modal view controller, or perhaps some additional state needs to be kept which allows the presenting view controller to decide whether or not it should present the modal view controller immediately.

James Bedford
  • 28,702
  • 8
  • 57
  • 64
  • Thanks for the reply, I'll have a look at this and see if its whats causing it – Kyle Goslan Sep 21 '12 at 21:35
  • 6
    @James you are correct, the view apparently is not in the hierarchy until *after* viewWillAppear has been resolved and once viewDidAppear has been called. If this were my question I would accept this answer ;) – Matt Mc Sep 24 '12 at 01:50
  • 6
    @james Thanks. Using the ViewDidAppear solved the problem for me too. Makes sense. – Ali Oct 07 '12 at 14:42
  • 53
    I wish I could upvote this twice. I just had this problem and came to the thread to find that I had already upvoted the last time I saw this. – Schrockwell Dec 15 '12 at 16:50
  • 7
    Note that when you change the VC in the viewDidAppear, this causes the execution of a segue, with Animation. Causes a flash/display of the background. – Vincent Jan 05 '13 at 10:17
  • 3
    Yes the trick is the viewWillAppear:(BOOL)animated as it's correct. One more important thing you have to call the super in the method, as [super viewDidAppear:animated]; without this it's not working. – BootMaker Mar 30 '13 at 20:56
  • 1
    Same problem as Vincent mentioned. When presenting in ViewWillAppear, i have flickering presenting view controller before presented is appears. – surfrider Apr 09 '14 at 08:04
  • 1
    For those who only looked at the upvoted answer, if you call if from AppDelegate, make sure it is called after [self.window makeKeyAndVisible]; Got this from another answer on this same page, but didn't want anyone to miss it – love2script12 May 12 '15 at 04:58
  • if you have storyboard you can use perform segue and main_queue **dispatch_async(dispatch_get_main_queue()) { }** – Eugene Braginets Jul 19 '16 at 18:12
  • 1
    In case anyone else has this problem, I had a similar problem using NSNotifications to trigger a method to presentViewController. I saw this solution but didn't think it applied to me as my methods weren't being called in viewDidLoad but then realized I had the addObserver for each notification in viewWillLoad --> 'view is not in the window hierarchy' error. When I moved the 'addObserver' to viewDidAppear, the errors disappeared. – user3000868 Jul 29 '16 at 12:28
  • 1
    @James Bedford Thanks ,you've saved my lot of time .Thank you very much :-) – Vaibhav Limbani Apr 28 '17 at 12:30
  • @James Bedford how to present view controller immediatly after the image is selected from gallery, because at the time uiimagepicker return me the image my controller is not appear – Jaydeep Vyas Dec 13 '17 at 09:12
  • This answer should be turned into an Xcode warning. – Teo Sartori Nov 23 '18 at 15:01
72

Another potential cause:

I had this issue when I was accidentally presenting the same view controller twice. (Once with performSegueWithIdentifer:sender: which was called when the button was pressed, and a second time with a segue connected directly to the button).

Effectively, two segues were firing at the same time, and I got the error: Attempt to present X on Y whose view is not in the window hierarchy!

Chris Nolet
  • 8,714
  • 7
  • 67
  • 92
  • 4
    I had the same error, and your answer here helped me figure out what was going on, it was indeed this error, fixed it because of you sir, thank you, +1 – samouray May 12 '15 at 07:39
  • 1
    I deleted the old segue and connected VC to VC. Is there a way to connect the button to the storyBoard to the VC because that way just keeps erroring for me? – MCB Jul 16 '15 at 22:25
  • I had same error, your answer solved my problem, thanks for your attention. Kind regards. – iamburak May 01 '16 at 19:27
  • 1
    lol, accidentally I also was creating two vc's: from button and performSegue, thanks for the tip!!! – Borzh Mar 31 '17 at 14:12
  • 1
    In my case I was calling `present(viewController, animated: true, completion: nil)` inside a loop. – Samo Apr 11 '18 at 19:43
39

viewWillLayoutSubviews and viewDidLayoutSubviews (iOS 5.0+) can be used for this purpose. They are called earlier than viewDidAppear.

Jonny
  • 15,955
  • 18
  • 111
  • 232
  • 4
    Still they are used also in other occasions so I think they might be called several times in a view's "lifetime". – Jonny Mar 08 '13 at 01:46
  • It's also not what the methods are for - as the name suggests. viewDidAppear is correct. Reading up on the view lifecycle is a good idea. – tooluser Sep 01 '13 at 06:13
  • This is the best solution. In my case, presenting in viewDidAppear causes a split second showing of the view controller before the modal is loaded, which is unacceptable. – TMilligan Nov 21 '13 at 03:33
  • This answer worked the best for me when trying to display an alert. The alert wouldn't show when I put it into viewDidLoad and viewWillAppear. – uplearned.com Jan 07 '16 at 21:31
28

For Display any subview to main view,Please use following code

UIViewController *yourCurrentViewController = [UIApplication sharedApplication].keyWindow.rootViewController;

while (yourCurrentViewController.presentedViewController) 
{
   yourCurrentViewController = yourCurrentViewController.presentedViewController;
}

[yourCurrentViewController presentViewController:composeViewController animated:YES completion:nil];

For Dismiss any subview from main view,Please use following code

UIViewController *yourCurrentViewController = [UIApplication sharedApplication].keyWindow.rootViewController;

while (yourCurrentViewController.presentedViewController) 
{
   yourCurrentViewController = yourCurrentViewController.presentedViewController;
}

[yourCurrentViewController dismissViewControllerAnimated:YES completion:nil];
Manann Sseth
  • 2,745
  • 2
  • 30
  • 50
abdul sathar
  • 2,395
  • 2
  • 28
  • 38
19

I also encountered this problem when I tried to present a UIViewController in viewDidLoad. James Bedford's answer worked, but my app showed the background first for 1 or 2 seconds.

After some research, I've found a way to solve this using the addChildViewController.

- (void)viewDidLoad
{
    ...
    [self.view addSubview: navigationViewController.view];
    [self addChildViewController: navigationViewController];
    ...
}
budiDino
  • 13,044
  • 8
  • 95
  • 91
sunkehappy
  • 8,970
  • 5
  • 44
  • 65
15

Probably, like me, you have a wrong root viewController

I want to display a ViewController in a non-UIViewController context,

So I can't use such code:

[self presentViewController:]

So, I get a UIViewController:

[[[[UIApplication sharedApplication] delegate] window] rootViewController]

For some reason (logical bug), the rootViewController is something other than expected (a normal UIViewController). Then I correct the bug, replacing rootViewController with a UINavigationController, and the problem is gone.

Arun
  • 3,406
  • 4
  • 30
  • 55
Sanbrother
  • 601
  • 5
  • 12
14

Swift 5 - Background Thread

If an alert controller is executed on a background thread then the "Attempt to present ... whose view is not in the window hierarchy" error may occur.

So this:

present(alert, animated: true, completion: nil)
    

Was fixed with this:

DispatchQueue.main.async { [weak self] in
    self?.present(alert, animated: true, completion: nil)
}
Marcy
  • 4,611
  • 2
  • 34
  • 52
8

TL;DR You can only have 1 rootViewController and its the most recently presented one. So don't try having a viewcontroller present another viewcontroller when it's already presented one that hasn't been dismissed.

After doing some of my own testing I've come to a conclusion.

If you have a rootViewController that you want to present everything then you can run into this problem.

Here is my rootController code (open is my shortcut for presenting a viewcontroller from the root).

func open(controller:UIViewController)
{
    if (Context.ROOTWINDOW.rootViewController == nil)
    {
        Context.ROOTWINDOW.rootViewController = ROOT_VIEW_CONTROLLER
        Context.ROOTWINDOW.makeKeyAndVisible()
    }

    ROOT_VIEW_CONTROLLER.presentViewController(controller, animated: true, completion: {})
}

If I call open twice in a row (regardless of time elapsed), this will work just fine on the first open, but NOT on the second open. The second open attempt will result in the error above.

However if I close the most recently presented view then call open, it works just fine when I call open again (on another viewcontroller).

func close(controller:UIViewController)
{
    ROOT_VIEW_CONTROLLER.dismissViewControllerAnimated(true, completion: nil)
}

What I have concluded is that the rootViewController of only the MOST-RECENT-CALL is on the view Hierarchy (even if you didn't dismiss it or remove a view). I tried playing with all the loader calls (viewDidLoad, viewDidAppear, and doing delayed dispatch calls) and I have found that the only way I could get it to work is ONLY calling present from the top most view controller.

Aggressor
  • 13,323
  • 24
  • 103
  • 182
  • This seems like a far more common issue that the many answers that have out voted yours. Unfortunate, this was extremely helpful – rayepps Jun 19 '18 at 06:13
  • yes all well and good but what is the solution... i have a background worker thread going to a server and displaying storyboards left right and centre and the entire methodology is bogus.. it's awful.. what should be a breeze is utterly a joke because al I want to do int he background thread is : `wait` `wait` `decide` `push screen to front` and it's IMPOSSIBLE is IOS???? – Mr Heelis May 09 '19 at 11:13
6

I had similar issue on Swift 4.2 but my view was not presented from the view cycle. I found that I had multiple segue to be presented at same time. So I used dispatchAsyncAfter.

func updateView() {

 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in

// for programmatically presenting view controller 
// present(viewController, animated: true, completion: nil)

//For Story board segue. you will also have to setup prepare segue for this to work. 
 self?.performSegue(withIdentifier: "Identifier", sender: nil)
  }
}
Ashim Dahal
  • 1,097
  • 13
  • 15
5

My issue was I was performing the segue in UIApplicationDelegate's didFinishLaunchingWithOptions method before I called makeKeyAndVisible() on the window.

Adam Johns
  • 35,397
  • 25
  • 123
  • 176
  • how? can you elaborate? i'm facing the same problem. this is my code let initialViewControlleripad : UIViewController = mainStoryboardIpad.instantiateViewController(withIdentifier: "SplashController") as UIViewController self.window?.rootViewController = initialViewControlleripad self.window?.makeKeyAndVisible() – shahtaj khalid Sep 13 '17 at 07:11
5

In my situation, I was not able to put mine in a class override. So, here is what I got:

let viewController = self // I had viewController passed in as a function,
                          // but otherwise you can do this


// Present the view controller
let currentViewController = UIApplication.shared.keyWindow?.rootViewController
currentViewController?.dismiss(animated: true, completion: nil)

if viewController.presentedViewController == nil {
    currentViewController?.present(alert, animated: true, completion: nil)
} else {
    viewController.present(alert, animated: true, completion: nil)
}
George
  • 25,988
  • 10
  • 79
  • 133
4

I've ended up with such a code that finally works to me (Swift), considering you want to display some viewController from virtually anywhere. This code will obviously crash when there is no rootViewController available, that's the open ending. It also does not include usually required switch to UI thread using

dispatch_sync(dispatch_get_main_queue(), {
    guard !NSBundle.mainBundle().bundlePath.hasSuffix(".appex") else {
       return; // skip operation when embedded to App Extension
    }

    if let delegate = UIApplication.sharedApplication().delegate {
        delegate.window!!.rootViewController?.presentViewController(viewController, animated: true, completion: { () -> Void in
            // optional completion code
        })
    }
}
igraczech
  • 2,408
  • 3
  • 25
  • 30
  • BTW to understand WHERE do I call this method from... it's the otherwise UI-less SDK library, that displays its own UI over your app in certain (undisclosed) case. – igraczech Feb 22 '16 at 17:45
  • You WILL crash and burn if anyone decides to embed you sdk in an app that has an extension. Pass a UIViewController to abuse into you sdk init method[s]. – Anton Tropashko Dec 07 '16 at 14:44
  • You're true Anton. This code was written when Extensions did not exist and SDK is not used in any of those yet. I've added a guard clause to skip this edge-case. – igraczech Dec 07 '16 at 14:58
4

You can call your segues or present, push codes inside this block:

override func viewDidLoad() {
    super.viewDidLoad()
    OperationQueue.main.addOperation {
        // push or present the page inside this block
    }
}
caglar
  • 279
  • 2
  • 5
3

I had the same problem. I had to embed a navigation controller and present the controller through it. Below is the sample code.

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    UIImagePickerController *cameraView = [[UIImagePickerController alloc]init];
    [cameraView setSourceType:UIImagePickerControllerSourceTypeCamera];
    [cameraView setShowsCameraControls:NO];

    UIView *cameraOverlay = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 768, 1024)];
    UIImageView *imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"someImage"]];
    [imageView setFrame:CGRectMake(0, 0, 768, 1024)];
    [cameraOverlay addSubview:imageView];

    [cameraView setCameraOverlayView:imageView];

    [self.navigationController presentViewController:cameraView animated:NO completion:nil];
//    [self presentViewController:cameraView animated:NO completion:nil]; //this will cause view is not in the window hierarchy error

}
resting
  • 16,287
  • 16
  • 59
  • 90
3

If you have AVPlayer object with played video you have to pause video first.

Vlad
  • 3,465
  • 1
  • 31
  • 24
3

I had the same issue. The problem was, the performSegueWithIdentifier was triggered by a notification, as soon as I put the notification on the main thread the warning message was gone.

Red
  • 1,425
  • 1
  • 10
  • 15
3

It's working fine try this.Link

UIViewController *top = [UIApplication sharedApplication].keyWindow.rootViewController;
[top presentViewController:secondView animated:YES completion: nil];
vijay
  • 1,475
  • 2
  • 16
  • 26
3

In case it helps anyone, my issue was extremely silly. Totally my fault of course. A notification was triggering a method that was calling the modal. But I wasn't removing the notification correctly, so at some point, I would have more than one notification, so the modal would get called multiple times. Of course, after you call the modal once, the viewcontroller that calls it it's not longer in the view hierarchy, that's why we see this issue. My situation caused a bunch of other issue too, as you would expect.

So to summarize, whatever you're doing make sure the modal is not being called more than once.

HotFudgeSunday
  • 1,403
  • 2
  • 23
  • 29
3

This kind of warning can mean that You're trying to present new View Controller through Navigation Controller while this Navigation Controller is currently presenting another View Controller. To fix it You have to dismiss currently presented View Controller at first and on completion present the new one. Another cause of the warning can be trying to present View Controller on thread another than main.

saltwat5r
  • 1,107
  • 13
  • 21
3

I fixed this error with storing top most viewcontroller into constant which is found within while cycle over rootViewController:

if var topController = UIApplication.shared.keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }
    topController.present(controller, animated: false, completion: nil)
    // topController should now be your topmost view controller
}
Diego Carrera
  • 2,245
  • 1
  • 13
  • 16
MarekB
  • 612
  • 6
  • 12
2

I fixed it by moving the start() function inside the dismiss completion block:

self.tabBarController.dismiss(animated: false) {
  self.start()
}

Start contains two calls to self.present() one for a UINavigationController and another one for a UIImagePickerController.

That fixed it for me.

Alper
  • 3,424
  • 4
  • 39
  • 45
1

You can also get this warning when performing a segue from a view controller that is embedded in a container. The correct solution is to use segue from the parent of container, not from container's view controller.

Borzh
  • 5,069
  • 2
  • 48
  • 64
1

Have to write below line.

self.searchController.definesPresentationContext = true

instead of

self.definesPresentationContext = true

in UIViewController

Coder_A_D
  • 340
  • 5
  • 20
1

It happened to me that the segue in the storyboard was some kind of broken. Deleting the segue (and creating the exact same segue again) solved the issue.

Cœur
  • 37,241
  • 25
  • 195
  • 267
vonox7
  • 1,081
  • 13
  • 24
1

With Swift 3...

Another possible cause to this, which happened to me, was having a segue from a tableViewCell to another ViewController on the Storyboard. I also used override func prepare(for segue: UIStoryboardSegue, sender: Any?) {} when the cell was clicked.

I fixed this issue by making a segue from ViewController to ViewController.

Devbot10
  • 1,193
  • 18
  • 33
1

I had this issue, and the root cause was subscribing to a button click handler (TouchUpInside) multiple times.

It was subscribing in ViewWillAppear, which was being called multiple times since we had added navigation to go to another controller, and then unwind back to it.

Wes
  • 1,059
  • 13
  • 18
1

With your main window, there will likely always be times with transitions that are incompatible with presenting an alert. In order to allow presenting alerts at any time in your application lifecycle, you should have a separate window to do the job.

/// independant window for alerts
@interface AlertWindow: UIWindow

+ (void)presentAlertWithTitle:(NSString *)title message:(NSString *)message;

@end

@implementation AlertWindow

+ (AlertWindow *)sharedInstance
{
    static AlertWindow *sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[AlertWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
    });
    return sharedInstance;
}

+ (void)presentAlertWithTitle:(NSString *)title message:(NSString *)message
{
    // Using a separate window to solve "Warning: Attempt to present <UIAlertController> on <UIViewController> whose view is not in the window hierarchy!"
    UIWindow *shared = AlertWindow.sharedInstance;
    shared.userInteractionEnabled = YES;
    UIViewController *root = shared.rootViewController;
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    alert.modalInPopover = true;
    [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
        shared.userInteractionEnabled = NO;
        [root dismissViewControllerAnimated:YES completion:nil];
    }]];
    [root presentViewController:alert animated:YES completion:nil];
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];

    self.userInteractionEnabled = NO;
    self.windowLevel = CGFLOAT_MAX;
    self.backgroundColor = UIColor.clearColor;
    self.hidden = NO;
    self.rootViewController = UIViewController.new;

    [NSNotificationCenter.defaultCenter addObserver:self
                                           selector:@selector(bringWindowToTop:)
                                               name:UIWindowDidBecomeVisibleNotification
                                             object:nil];

    return self;
}

/// Bring AlertWindow to top when another window is being shown.
- (void)bringWindowToTop:(NSNotification *)notification {
    if (![notification.object isKindOfClass:[AlertWindow class]]) {
        self.hidden = YES;
        self.hidden = NO;
    }
}

@end

Basic usage that, by design, will always succeed:

[AlertWindow presentAlertWithTitle:@"My title" message:@"My message"];
Cœur
  • 37,241
  • 25
  • 195
  • 267
1

Sadly, the accepted solution did not work for my case. I was trying to navigate to a new View Controller right after unwind from another View Controller.

I found a solution by using a flag to indicate which unwind segue was called.

@IBAction func unwindFromAuthenticationWithSegue(segue: UIStoryboardSegue) {
    self.shouldSegueToMainTabBar = true
}

@IBAction func unwindFromForgetPasswordWithSegue(segue: UIStoryboardSegue) {
    self.shouldSegueToLogin = true
}

Then present the wanted VC with present(_ viewControllerToPresent: UIViewController)

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    if self.shouldSegueToMainTabBar {
        let mainTabBarController = storyboard.instantiateViewController(withIdentifier: "mainTabBarVC") as! MainTabBarController
        self.present(mainTabBarController, animated: true)
        self.shouldSegueToMainTabBar = false
    }
    if self.shouldSegueToLogin {
        let loginController = storyboard.instantiateViewController(withIdentifier: "loginVC") as! LogInViewController
        self.present(loginController, animated: true)
        self.shouldSegueToLogin = false
    }
}

Basically, the above code will let me catch the unwind from login/SignUp VC and navigate to the dashboard, or catch the unwind action from forget password VC and navigate to the login page.

Fan Jin
  • 2,412
  • 17
  • 25
1

I found this bug arrived after updating Xcode, I believe to Swift 5. The problem was happening when I programatically launched a segue directly after unwinding a view controller.

The solution arrived while fixing a related bug, which is that the user was now able to unwind segues by swiping down the page. This broke the logic of my program.

It was fixed by changing the Presentation mode on all the view controllers from Automatic to Full Screen.

You can do it in the attributes panel in interface builder. Or see this answer for how to do it programatically.

Tim MB
  • 4,413
  • 4
  • 38
  • 48
1

Swift 5

I call present in viewDidLayoutSubviews as presenting in viewDidAppear causes a split second showing of the view controller before the modal is loaded which looks like an ugly glitch

make sure to check for the window existence and execute code just once

var alreadyPresentedVCOnDisplay = false

override func viewDidLayoutSubviews() {
        
    super.viewDidLayoutSubviews()
    
    // we call present in viewDidLayoutSubviews as
    // presenting in viewDidAppear causes a split second showing 
    // of the view controller before the modal is loaded
    
    guard let _ = view?.window else {
        // window must be assigned
        return
    }
    
    if !alreadyPresentedVCOnDisplay {
        alreadyPresentedVCOnDisplay = true
        present(...)
    }
    
}
Peter Lapisu
  • 19,915
  • 16
  • 123
  • 179
1

This happened to me when I was trying to present onto my navigationController when its view has not been presented onto the view hierarchy yet. The way I solved this is to listen to the NavigationControllerDelegate's didShow method. Once the didShow method is called, I know I can present onto my navigationController.

Note: using dispatchQueueAsync.await(.now()) { //present } does work, but it is hacky and prone to bugs if the view takes long to display onto the view hierarchy.

Mocha
  • 2,035
  • 12
  • 29
1
    let alert = UIAlertController(title: "", message: "YOU SUCCESSFULLY\nCREATED A NEW\nALERT CONTROLLER", preferredStyle: .alert)
    func okAlert(alert: UIAlertAction!)
    {
        
    }
    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: okAlert))
    
    let scenes = UIApplication.shared.connectedScenes
    let windowScene = scenes.first as? UIWindowScene
    let window = windowScene?.windows.first
    var rootVC = window?.rootViewController
    
    if var topController = rootVC
    {
        while let presentedViewController = topController.presentedViewController
        {
            topController = presentedViewController
        }
        rootVC = topController
    }
    rootVC?.present(alert, animated: true, completion: nil)
daj mi spokój
  • 248
  • 1
  • 2
  • 8
0

I just had this issue too, but it had nothing to do with the timing. I was using a singleton to handle scenes, and I set it as the presenter. In other words "Self" wasn't hooked up to anything. I just made its inner "scene" the new presenter and voila, it worked. (Voila loses its touch after you learn its meaning, heh).

So yeah, it's not about "magically finding the right way", it's about understanding where your code stands and what it's doing. I'm happy Apple gave such a plain-English warning message, even with emotion to it. Kudos to the apple dev who did that!!

Stephen J
  • 2,367
  • 2
  • 25
  • 31
0

If other solutions does not look good for some reason, you can still use this good old workaround of presenting with the delay of 0, like this:

dispatch_after(0, dispatch_get_main_queue(), ^{
    finishViewController *finished = [self.storyboard instantiateViewControllerWithIdentifier:@"finishViewController"];
    [self presentViewController:finished animated:NO completion:NULL];    
});

While I've seen no documented guarantee that your VC would be on the view hierarchy on the time dispatch block is scheduled to execution, I've observed it would work just fine.

Using delay of e.g. 0.2 sec is also an option. And the best thing - this way you don't need to mess with boolean variable in viewDidAppear:

Dannie P
  • 4,464
  • 3
  • 29
  • 48
  • 2
    While this might work now, there's no guarantee that Apple will change behavior and break this on you in the future. Then when you go back to look at your code to try and fix things again, you'll wonder why you were doing this seemingly unnecessary dispatch. – Cruinh May 07 '15 at 20:02
  • Dispatch with 0 time has saved me quite a number of times already - sometimes things that just should logically work don't normally work without it. So just make comments for yourself and others on why you do unobvious things (not only such a dispatch) and you should be fine. – Dannie P May 08 '15 at 06:43
  • 2
    You are just hacking around the problem and not solving it. – Michael Peterson Jul 25 '16 at 15:15
0

This works for to present any view controller ,if you have navigation controller available. self.navigationController?.present(MyViewController, animated: true, completion: nil) Also , I can able to present alerts and mail controller also.

Gurpreet Singh
  • 803
  • 9
  • 16
0

The message appear as warning and sometimes the code refuses to work. (!Needs Citation: Newer SDK's might have strict rules).

I have encountered it for more than one reason, mostly complicated viewcontroller scenarios. Here's an example.

Scenario: MainViewController (responsible to load: ViewControllerA & ViewControllerB)

Present ViewControllerA from MainViewController and without dismissing the ViewControllerA you try to present viewControllerB from MainViewController (using a delegate method).

In this scenario, you'd have to make sure your ViewControllerA is dismissed and then the ViewControllerB is called.

Because after presenting ViewControllerA (ViewControllerA becomes responsible for displaying views and viewcontrollers and when MainViewController attempts to load another viewcontoller, it refuses to work with throwing a warning).

Naveed Abbas
  • 1,157
  • 1
  • 14
  • 37
0

Using storyboards, I noticed I had a button connected to two @IBActions, these two @IBActions functions present a ViewController each. After I remove one connection, the problem was solve.

pableiros
  • 14,932
  • 12
  • 99
  • 105
0

It Mostly happens when you call Alert after working on some background thins like Api hit or specially Core Data services. Call Alert in your App's Main thread.

DispatchQueue.main.async {
   // call your alert in this main thread.
   Constants.alert(title: "Network Error!", message: "Please connect to internet and try again.", controller: self)
}
manu
  • 556
  • 2
  • 9
  • 29