5

I am using an unwind segue to unwind to the initial view controller in my storyboard. The unwind works great, I implemented this method in my initial view controller:

- (IBAction) unwindToInitialViewController:(UIStoryboardSegue *) unwindSegue {

}

However if I try an segue to another view controller after I do the unwind I get the following error:

Warning: Attempt to present on whose view is not in the window hierarchy!

It seems like this only occurs if I unwind to the view controller that is checked as 'Initial View Controller' in the storyboard. Is this a bug? Should I be able to unwind to that initial controller? Other ideas?

EDIT:

Here is how I perform the second segue:

[self performSegueWithIdentifier:@"mySegue" sender:nil];

I should note that this is a login/logout problem. When I log in the first time the segue from my login controller to my next controller works. When I logout I unwind to the initial view controller. I then log in again and the segue from my login controller to the next controller does not work.

EDIT 2:

From more research I have found its because I am using a delegate for my login. Login is async, I make a call with AFNetworking and when its done I call my login delegate (the login VC in this case). At that point the login VC can segue to the view.

Login code:

- (void) login: (NSDictionary *) parameters {
    [http.manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, NSDictionary *response) {
       [self.loginDelegate loginSuccess:response]; 
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
       [self.loginDelegate loginFailure:error]; 
    }];
}

My login VC which is the delegate:

- (void) loginSuccess:(NSDictionary *) response {
    // setup user info based on response
    ...
    // Segue 
    [self performSegueWithIdentifier:@"loginSuccessSegue" sender:nil];
}

I have checked that I am in the main thread when and I segue and still no luck. I know that AFNetworking always calls the success/failure blocks on the main thread too.

The tricky part. If I change that above code to use a block and not a delegate the storyboard/segue does not get messed up and I can login and logout many times with no problem.

Why does the segue work the first time with the delegate pattern, but on logout (unwind), can I not use that segue again?

EDIT 3:

More investigation shows that on unwind my login VC viewDidAppear is called twice. On initial unwind the view looks to still be on the stack, show it shows quickly and viewDidAppear is called. However this is animated away quickly and viewWillAppear is called a second time with a different VC. I think this might be the root of the problem. Why when I unwind to that VC is it animated away only to be animated back in?

lostintranslation
  • 20,811
  • 42
  • 129
  • 220

1 Answers1

1

Please check, whether your loginDelegate is nil during the second login attempt. If it is nil the "delegate calls" will just go to nowhere. Also please check whether the loginDelegate points to the instance you expect. If it points to an "old" instance, the wrong views may be tried to be presented.

The set of methods viewDidLoad, viewDidAppear, viewWillAppear, etc. can be called in unexpected order especially when going back in navigation or presenting an ad and coming back from it. If you have different initialization/setup tasks distributed among these methods, you may end up with a partly initialized view controller.

(Thinking about the problem I lost your statement about the error encountered, so probably the delegate is not nil.)

EDIT:

I ran one of my tiny unwind test projects and there log the viewDidAppear calls:

viewDidAppear: <ViewController: 0x7a687700>
viewDidAppear: <VC2: 0x7a70e970>
viewDidAppear: <VC3: 0x7a694d50>
unwind target
viewDidAppear: <VC2: 0x7a70e970>
viewDidAppear: <ViewController: 0x7a687700>
viewDidAppear: <VC2: 0x7a71b790>
viewDidAppear: <VC3: 0x7a694d20>
unwind target
viewDidAppear: <VC2: 0x7a71b790>
viewDidAppear: <ViewController: 0x7a687700>

Doing the unwind in VC3 briefly shows VC2 and eventually ends up at the target ViewController. Now the second "login" leads to different instances of the view controllers.

Do you keep references to "old" view controllers?

Another reason might be, that your "logout" detection fires twice (once when coming along the unwind and one more time when an intermediate or the initial view controller detects the need to login?).

Rainer Schwarze
  • 4,462
  • 1
  • 24
  • 42
  • It's not nil. Delegate call happens. Segue tries to go and cannot present view, hence the error message. – lostintranslation Sep 28 '15 at 19:37
  • @lostintranslation I posted the answer and immediately remembered the error message :-) I updated the answer. – Rainer Schwarze Sep 28 '15 at 19:41
  • Same instance :(. Verified in debugger. – lostintranslation Sep 28 '15 at 19:42
  • @lostintranslation After I ran a little test, I noticed your edits regarding different instances. I updated my answer with more details. Can you check whether any of that is relevant to the issue? - Thanks and best wishes – Rainer Schwarze Sep 30 '15 at 14:00
  • thanks so much! I think you are exactly right. For some reason when I unwind my login VC is shown for a second, then it is dismissed and the storyboard shows a new one. I had a bug in my delegate assignment that was due to this behavior. – lostintranslation Sep 30 '15 at 18:38
  • Any idea why unwind shows a VC only to dismiss it and show it again? If so is there a way to prevent that. I fixed my bug, but the UI is ugly when the VC is dismissed and then re animated back in. – lostintranslation Sep 30 '15 at 18:39
  • My "classic" reason for similar behaviour in my test app is wiring a segue from a button instead of the yellow view controller icon when I want a manual segue. Calling performSegue in the button handler calls the segue twice. -- Is it possible that you have two instances of the VC on the view controller stack? (You may log them using self.navigationController.viewControllers.) – Rainer Schwarze Sep 30 '15 at 20:27