5

I have an iOS app that has a connection to a server. If we get disconnected, I want to be able to dismiss the top view controllers to get back to a "connecting to server" view controller. The problem is that a disconnection can occur at any time, including during a transition between view controllers.

The view controller hierarchy is like so:

  1. ConnectingToServerViewController
  2. SignInViewController
  3. MainAppViewController
  4. Other view controllers

When a disconnection is detected I want the view hierarchy to collapse back to:

  1. ConnectingToServerViewController

So when a disconnection is detected, this method is called on the ConnectingToServerViewController to dismiss anything that it has presented and go back to attempting to connect to server:

- (void)restartSession
{
    if (self.presentedViewController) {
        [self dismissViewControllerAnimated:NO completion:nil];
    }
}

However, if I try to dismiss while a view transition is occurring, I get errors such as

*** Assertion failure in -[UIWindowController transition:fromViewController:toViewController:target:didEndSelector:], /SourceCache/UIKit/UIKit-2380.17/UIWindowController.m:211

attempt to dismiss modal view controller whose view does not currently appear. self = <YYYYYViewController: 0x2089c8a0> modalViewController = <XXXXXViewController: 0x208e6610>
attempt to dismiss modal view controller whose view does not currently appear. self = <WWWWWWViewController: 0x1fd9e990> modalViewController = <YYYYYViewController: 0x2089c8a0>

The first of which will crash the app, the second will just not dismiss anything and continue to show the current presented view controller.

Thoughts:

  1. delays won't work since we don't know when to start the delay
  2. is there a way to track when view transitions complete?
  3. should all view controllers override willAppear, didAppear and alert the app when it is safe to dismiss?
  4. perhaps instead of dismiss, I should just set a new root view controller?
  5. I've made sure that all overridden view(will|did)(dis)?appear methods call the appropriate super method.
  6. Any solution that requires all view controllers to override view(did|will)appear methods to track state sounds like it could cause issues if we forget to set the base class for a new view controller.
Vineet Singh
  • 4,009
  • 1
  • 28
  • 39
Nick Sonneveld
  • 3,356
  • 6
  • 39
  • 48
  • if you can set any flag variable in viewController and when you are disconnected from server then if view is not loaded then you can change flag from restrtSesssion method. so that when view loads it will automatically dismiss it self. you can use some logic like that... hope it will help.. – Armaan Stranger Jul 23 '13 at 04:51
  • I kind of touched upon this near the end. I wanted to avoid solutions that require all view controllers to have to subclass a special viewcontroller class just to keep track of state. – Nick Sonneveld Jul 23 '13 at 04:56
  • What about dismissing it with a delay or on the main queue by placing the dismissal inside: NSOperationQueue.mainQueue().addOperationWithBlock { // dismiss } // swift code – Erik Engheim Nov 12 '14 at 13:51

4 Answers4

0

It seems like you are trying to dismiss the view controller when it is not currently on screen. To check if it is on screen you could use:

if (self.presentedViewController.view.window) 
{
    [self dismissViewControllerAnimated:NO completion:nil];
}
else 
{
    self.presentedViewController = nil;
}
Shaik Riyaz
  • 11,204
  • 7
  • 53
  • 70
Jamie
  • 5,090
  • 28
  • 26
  • But I will still need to dismiss when the presented view controller is finally visible though? – Nick Sonneveld Jul 23 '13 at 04:54
  • Then if it's not on screen, you can set a flag in your viewController to indicate that the modal view requires dismissal. Then in your viewWillAppear: you can check the flag and dismiss it if necessary. Not sure on this cause I've never had to do it, but just an idea. – Jamie Jul 23 '13 at 05:02
  • Hrm, we might have to go that way but I wanted to avoid having every view controller have to check if it needs to dismiss itself. It feels like we could forget to do this for a new viewcontroller. Plus we have a lot of view controllers. – Nick Sonneveld Jul 23 '13 at 05:04
  • Have you tried setting the modal view controller to nil if it is off screen? – Jamie Jul 23 '13 at 05:07
0

Do something like this. Try this out once,

UIViewController *controller = self.presentingViewController; //THIS LINE IS IMP
[self dismissViewControllerAnimated:YES
                                 completion:^{
                                     [controller presentViewController:adminViewController animated:YES completion:nil];
                                     adminViewController.view.superview.frame    = CGRectMake(1024/2 - 400, 768/2 - 280, 800 , 560);//it's important to do this after
                                     [adminViewController release];
                                 }]; 
βhargavḯ
  • 9,786
  • 1
  • 37
  • 59
  • An assertion can occur before the completion block is called. – Nick Sonneveld Jul 23 '13 at 04:53
  • when calling dismissViewControllerAnimated:completion, this assertion occurs: *** Assertion failure in -[UIWindowController transition:fromViewController:toViewController:target:didEndSelector:], /SourceCache/UIKit/UIKit-2380.17/UIWindowController.m:211 before the completion block is even called. – Nick Sonneveld Jul 23 '13 at 04:57
  • If you are presenting next VC, just check once you have code in ViewDidAppear. – βhargavḯ Jul 23 '13 at 05:00
  • It doesn't sound right that every view controller should check in viewDidAppear. – Nick Sonneveld Jul 23 '13 at 05:01
0

One way that has worked for me is to assign a new view controller to the root view controller. That way, views in the old hierarchy can animate and transition to their hearts content while we have new controllers.

eg

- (void)restartSession
{
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
    ConnectingToServerViewController *vc = [storyboard instantiateViewControllerWithIdentifier:@"ConnectingToServerViewController"];
    vc.modalPresentationStyle = UIModalPresentationFullScreen;
    [UIApplication sharedApplication].delegate.window.rootViewController = vc;
}

I'm not sure if I'm aware of all the downsides to this though. Perhaps the old view controllers will never get freed because of a dangling strong reference? We're no longer reusing ConnectingToServerViewController, we have to recreate that each time.

I based the code on what I saw in this answer for Managing and dismissing Multiple View Controllers in iOS.

Community
  • 1
  • 1
Nick Sonneveld
  • 3,356
  • 6
  • 39
  • 48
0

I will answer in order.

is there a way to track when view transitions complete?

You could try with the UINavigationControllerDelegate (if you are using one of those). Other approach could be using a custom animator.

should all view controllers override willAppear, didAppear and alert the app when it is safe to dismiss?

That's an option. You are free to do it if you want. Another option is not to do that. I think that container view controllers such as navigation controller has better approaches.

I should just set a new root view controller?

I would suggest to do the opposite. I would set the SignInViewController / MainAppViewController as the root flow, and present modally ConnectingToServerViewController on demand. In my opinion that's a healthier approach.

Hope it helps.

facumenzella
  • 559
  • 3
  • 10