[ios] modal View controllers - how to display and dismiss

I'm breaking my head for the last one week on how to solve the issue with showing and dismissing multiple view controllers. I have created a sample project and pasting the code directly from the project. I have 3 view controllers with their corresponding .xib files. MainViewController, VC1 and VC2. I have two buttons on the main view controller.

- (IBAction)VC1Pressed:(UIButton *)sender
{
    VC1 *vc1 = [[VC1 alloc] initWithNibName:@"VC1" bundle:nil];
    [vc1 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc1 animated:YES completion:nil];
}

This opens VC1 with no issues. In VC1, I have another button that should open VC2 while at the same time dismiss VC1.

- (IBAction)buttonPressedFromVC1:(UIButton *)sender
{
    VC2 *vc2 = [[VC2 alloc] initWithNibName:@"VC2" bundle:nil];
    [vc2 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc2 animated:YES completion:nil];
    [self dismissViewControllerAnimated:YES completion:nil];
} // This shows a warning: Attempt to dismiss from view controller <VC1: 0x715e460> while a presentation or dismiss is in progress!


- (IBAction)buttonPressedFromVC2:(UIButton *)sender
{
    [self dismissViewControllerAnimated:YES completion:nil];
} // This is going back to VC1. 

I want it go back to the main view controller while at the same time VC1 should have been removed from memory for good. VC1 should only show up when I click on the VC1 button on the main controller.

The other button on the Main view controller should also be able to display VC2 directly bypassing VC1 and should come back to the main controller when a button is clicked on VC2. There is no long running code, loops or any timers. Just bare bone calls to view controllers.

The answer is


I have solved the issue by using UINavigationController when presenting. In MainVC, when presenting VC1

let vc1 = VC1()
let navigationVC = UINavigationController(rootViewController: vc1)
self.present(navigationVC, animated: true, completion: nil)

In VC1, when I would like to show VC2 and dismiss VC1 in same time (just one animation), I can have a push animation by

let vc2 = VC2()
self.navigationController?.setViewControllers([vc2], animated: true)

And in VC2, when close the view controller, as usual we can use:

self.dismiss(animated: true, completion: nil)

I wanted this:

MapVC is a Map in full screen.

When I press a button, it opens PopupVC (not in full screen) above the map.

When I press a button in PopupVC, it returns to MapVC, and then I want to execute viewDidAppear.

I did this:

MapVC.m: in the button action, a segue programmatically, and set delegate

- (void) buttonMapAction{
   PopupVC *popvc = [self.storyboard instantiateViewControllerWithIdentifier:@"popup"];
   popvc.delegate = self;
   [self presentViewController:popvc animated:YES completion:nil];
}

- (void)dismissAndPresentMap {
  [self dismissViewControllerAnimated:NO completion:^{
    NSLog(@"dismissAndPresentMap");
    //When returns of the other view I call viewDidAppear but you can call to other functions
    [self viewDidAppear:YES];
  }];
}

PopupVC.h: before @interface, add the protocol

@protocol PopupVCProtocol <NSObject>
- (void)dismissAndPresentMap;
@end

after @interface, a new property

@property (nonatomic,weak) id <PopupVCProtocol> delegate;

PopupVC.m:

- (void) buttonPopupAction{
  //jump to dismissAndPresentMap on Map view
  [self.delegate dismissAndPresentMap];
}

Example in Swift, picturing the foundry's explanation above and the Apple's documentation:

  1. Basing on the Apple's documentation and the foundry's explanation above (correcting some errors), presentViewController version using delegate design pattern:

ViewController.swift

import UIKit

protocol ViewControllerProtocol {
    func dismissViewController1AndPresentViewController2()
}

class ViewController: UIViewController, ViewControllerProtocol {

    @IBAction func goToViewController1BtnPressed(sender: UIButton) {
        let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
        vc1.delegate = self
        vc1.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal
        self.presentViewController(vc1, animated: true, completion: nil)
    }

    func dismissViewController1AndPresentViewController2() {
        self.dismissViewControllerAnimated(false, completion: { () -> Void in
            let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
            self.presentViewController(vc2, animated: true, completion: nil)
        })
    }

}

ViewController1.swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.dismissViewController1AndPresentViewController2()
    }

}

ViewController2.swift

import UIKit

class ViewController2: UIViewController {

}
  1. Basing on the foundry's explanation above (correcting some errors), pushViewController version using delegate design pattern:

ViewController.swift

import UIKit

protocol ViewControllerProtocol {
    func popViewController1AndPushViewController2()
}

class ViewController: UIViewController, ViewControllerProtocol {

    @IBAction func goToViewController1BtnPressed(sender: UIButton) {
        let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
        vc1.delegate = self
        self.navigationController?.pushViewController(vc1, animated: true)
    }

    func popViewController1AndPushViewController2() {
        self.navigationController?.popViewControllerAnimated(false)
        let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
        self.navigationController?.pushViewController(vc2, animated: true)
    }

}

ViewController1.swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.popViewController1AndPushViewController2()
    }

}

ViewController2.swift

import UIKit

class ViewController2: UIViewController {

}

Radu Simionescu - awesome work! and below Your solution for Swift lovers:

@IBAction func showSecondControlerAndCloseCurrentOne(sender: UIButton) {
    let secondViewController = storyboard?.instantiateViewControllerWithIdentifier("ConrollerStoryboardID") as UIViewControllerClass // change it as You need it
    var presentingVC = self.presentingViewController
    self.dismissViewControllerAnimated(false, completion: { () -> Void   in
        presentingVC!.presentViewController(secondViewController, animated: true, completion: nil)
    })
}

I think you misunderstood some core concepts about iOS modal view controllers. When you dismiss VC1, any presented view controllers by VC1 are dismissed as well. Apple intended for modal view controllers to flow in a stacked manner - in your case VC2 is presented by VC1. You are dismissing VC1 as soon as you present VC2 from VC1 so it is a total mess. To achieve what you want, buttonPressedFromVC1 should have the mainVC present VC2 immediately after VC1 dismisses itself. And I think this can be achieved without delegates. Something along the lines:

UIViewController presentingVC = [self presentingViewController];
[self dismissViewControllerAnimated:YES completion:
 ^{
    [presentingVC presentViewController:vc2 animated:YES completion:nil];
 }];

Note that self.presentingViewController is stored in some other variable, because after vc1 dismisses itself, you shouldn't make any references to it.


Examples related to ios

Adding a UISegmentedControl to UITableView Crop image to specified size and picture location Undefined Symbols error when integrating Apptentive iOS SDK via Cocoapods Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Autoresize View When SubViews are Added Warp \ bend effect on a UIView? Speech input for visually impaired users without the need to tap the screen make UITableViewCell selectable only while editing Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

Examples related to objective-c

Adding a UISegmentedControl to UITableView Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Warp \ bend effect on a UIView? Use NSInteger as array index Detect if the device is iPhone X Linker Command failed with exit code 1 (use -v to see invocation), Xcode 8, Swift 3 ITSAppUsesNonExemptEncryption export compliance while internal testing? How to enable back/left swipe gesture in UINavigationController after setting leftBarButtonItem? Change status bar text color to light in iOS 9 with Objective-C

Examples related to uiviewcontroller

How to set Status Bar Style in Swift 3 UIView touch event in controller How to lock orientation of one view controller to portrait mode only in Swift Programmatically navigate to another view controller/scene Adding a view controller as a subview in another view controller Changing the Status Bar Color for specific ViewControllers using Swift in iOS8 Get top most UIViewController Instantiate and Present a viewController in Swift How to check if a view controller is presented modally or pushed on a navigation stack? Add a UIView above all, even the navigation bar

Examples related to presentviewcontroller

Warning: Attempt to present * on * whose view is not in the window hierarchy - swift Swift presentViewController iOS: present view controller programmatically How to call a View Controller programmatically? modal View controllers - how to display and dismiss

Examples related to dismissviewcontroller

modal View controllers - how to display and dismiss