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.
This question is related to
ios
objective-c
uiviewcontroller
presentviewcontroller
dismissviewcontroller
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:
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 {
}
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.
Source: Stackoverflow.com