Modal Segue Chain

2019-04-29 23:18发布

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?

4条回答
你好瞎i
2楼-- · 2019-04-30 00:04

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).

查看更多
\"骚年 ilove
3楼-- · 2019-04-30 00:06
[self.navigationController popToRootViewControllerAnimated:YES];
查看更多
走好不送
4楼-- · 2019-04-30 00:09

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();        
}
查看更多
【Aperson】
5楼-- · 2019-04-30 00:14

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];
}
查看更多
登录 后发表回答