Modal Segue Chain
Asked Answered
L

4

5

I have an iOS app that has a log in view (LognnViewController) and once a user is successfully authenticated they are taken to another view (DetailEntryViewController) to enter some simple details.
Once the details are entered the user is taken to the main part of the app that consists of a tab controller (TabViewController) that holds a variety of other views. The LogInViewController performs a modal segue to the DetailEntryViewController and the DetailEntryViewController then performs a modal segue to the TabViewController so I have kind of a modal segue chain going to get into the app. When a user logs out I want to go all the way back to the LogInViewController but when I do a:

[self.presentingViewController dismissModalViewControllerAnimated:YES];

...it pops the TabViewController and I end up back at the DetailEntryViewController instead of the first LogInViewController. Is there any way I can pop back to the first view controller easily or does doing this modal segue chain thing prevent me from that. I got the bright idea to put some code in the DetailEntryViewController viewWillAppear: that would automagically pop itself if the user had logged out but apparent making calls to dismiss a modal controller are not allowed in viewWillAppear: viewDidLoad:, etc.

Any ideas on how to make this happen?

Longlegged answered 5/12, 2012 at 17:1 Comment(0)
O
8

I think this is not the best structure to implement your app. Modal controllers are supposed to be for temporary interruptions to the flow of the program, so using a modal to get to your main content is not ideal. The way I would do this is to make your tab bar controller the root view controller of the window, and then in the first tab's controller, present the login controller modally from the viewDidAppear method, so it will appear right away (you will briefly see the first tab's view unless you uncheck the "animates" box in the segue's attributes inspector). Present the details controller from that one, and then dismiss both modal controllers to get back to your main content. When the user logs out, just present that login controller again. I implement this idea like this. In the first tab's view controller:

- (void)viewDidLoad {
    [super viewDidLoad];
    _appStarting = YES;
}

-(void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    if (_appStarting) {
       [self performSegueWithIdentifier:@"Login" sender:self];
        _appStarting = NO;
    }
}

Then in the last (second in your case) modal view controller, I have a button method:

-(IBAction)goBackToMain:(id)sender {
    [self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
}
Offish answered 5/12, 2012 at 17:39 Comment(0)
L
1

Figured it out myself...just had to go up one more level to get to the "root" view controller (LogInViewController) and found that this did the trick:

[[self.presentingViewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];

As I said I'm just getting the presentingViewController (DetailEntryViewController) and then going up one more level and getting that controller's presenter (LogInViewController).

Longlegged answered 5/12, 2012 at 17:41 Comment(2)
As I said in my answer, I don't think this is a good structure. You, and many other people on this board are misusing the modal segue -- modal controllers are meant to be temporary interruptions to the program's flow, not a way to get to your main content. It works, but I don't think it's a good design.Offish
I completely agree here and I do believe the structure you chose will lead to confusion of how the navigation stack should be used. I see no reason to present a modal view to another modal view and then navigating past that. Can you explain your logic here? Modal views are meant to be displayed then removed. If you are looking to navigate beyond that then I don't believe this is the correct choice and it would be better to fix the logic rather than dive too deep into this structure. But to each is own. ;-)Vereeniging
B
1

I had similar problem and my "modal segue chain" was not limited. I agree with the arguments in the answer and comments below about modal segues designed for different thing, but I liked the "horizontal flip" animation of modal segues and I couldn't find the easier way to replicate them... Also in general I don't see anything wrong in using things that were designed for one thing to achieve some other thing, like chaining modal controllers. Repeated "partial curl" animation can also apply to some scenario in some app.

So I implemented the stack of modal controllers as a property of controller:

@interface ModalViewController : UIViewController
@property (nonatomic, retain) NSMutableArray *modalControllers;
@end

When the first modal segue is executed the stack is created in prepareForSegue method of controller that is not modal:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"modalSegue"]) {
        ModalViewController *controller =
            (ModalViewController *)[segue destinationViewController];

        controller.modalControllers = [NSMutableArray arrayWithObject: controller];
    }
}

When one modal controller moves to another the destination is added to the stack (in the method of ModalViewCotroller)

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"modalSegue"]) {
        ModalViewController *destController =
            (ModalViewController *)[segue destinationViewController];

        // add destination controller to stack
        destController.modalControllers = _modalControllers;
        [destController.modalControllers addObject: destController];
    }
}

To dismiss the whole stack at once was the most tricky part - you can't dismiss the previous controller before the next finished dismissing, so the cycle did not work, only recursive blocks did the trick, with avoiding the memory leak being the trickiest (I'm yet to check it, but I relied on this):

- (IBAction)dismissAllModalControllers: (id)sender
{
    // recursive block that dismisses one auth controller
    // all these dances are to avoid leaks with ARC
    typedef void (^voidBlockType)();
    __block void (^dismissController) ();
    voidBlockType __weak dismissCopy = ^void(void) {
        dismissController();
    };
    dismissController = ^void(void) {
        int count = [_modalControllers count];
        if (count > 0) {
            // get last controller
            UIViewController *controller =
                (UIViewController *)[_modalControllers lastObject];
            // remove last controller
            [_modalControllers removeLastObject];
            // dismiss last controller
            [controller
                // the first controller in chain is dismissed with animation
                dismissViewControllerAnimated: count == 1 ? YES : NO
                // on completion call the block that calls this block recursively
                completion: dismissCopy]; 
        }
    };

    // this call dismisses all modal controllers
    dismissController();        
}
Backhanded answered 23/5, 2013 at 18:37 Comment(0)
V
0
[self.navigationController popToRootViewControllerAnimated:YES];
Vereeniging answered 5/12, 2012 at 17:3 Comment(3)
There is no navigation controller...it is a tab view controller shown by a two step modal segue chain.Longlegged
You are "popping" back to a rootview without a navigationcontroller? The behavior you are asking for is the exact reason a UINavigationController was created. Unless I am missing something?Vereeniging
Sorry I'm using "pop" in the generic sense of removing a view controller. I do not have a root navigation controller - I have two standard view controllers that are the "tunnel" into the app via modal segues and then a tab view controller that contains other views. I figured out the answer, though...see below.Longlegged

© 2022 - 2024 — McMap. All rights reserved.