How can I dismiss a modal and then immediately push another view?
I am adding this code in a didSelectRowAtIndexPath:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ChatTableViewController *chatVC = (ChatTableViewController*)[storyboard instantiateViewControllerWithIdentifier:#"chatVC"];
[self dismissViewControllerAnimated:YES completion:^{
[[self navigationController] pushViewController:chatVC animated:YES];
}];
The modal view dismisses, but nothing happens after. Any ideas?
You can't push a view controller into a view dismissed, if it's dismissed it disappears so it's not logical to push another view, cause the parent view controller is deleted. Probably you have this:
- ViewController 1 --> Modal ViewController 2 -->Wanted to dismiss VC2 and push VC3
What you have to do is
- ViewController 1 --> Modal ViewController 2 --> Dismiss VC2 --> Push VC3 on VC1
You can do it with notifications. The most efficient way is to use delegates, create a delegate on VC2 that notifies V1 when it dismisses and then just push VC3.
No, it cant be done that quickly as far as I know, you need to add a delay betwwn the dissmiss and the navigation of another controller, only then will it execute else it would crash an error saying, trying to push a controller while dismissing other
This answer tries to solve the original problem asked above.
A,B,C viewControllers. A is the root VC of a navigationController.
A-> modally_presents -> B-> dismissing_modally_prsented_B_and_automatically_pushing -> C
Note: All presentation and dismissals are done using segues.
I have marked important sequence of actions.
class CViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
/************************************************************/
protocol BDelegate: class {
func BDelegateFunc(someRandomString: String)
}
class BViewController: UIViewController {
weak var delegate: BDelegate?
var name: String?
override func viewDidLoad() {
super.viewDidLoad()
name = "swappy"
}
#IBAction func btnClicked(_ sender: Any) {
// 3 DISMISS using segue
performSegue(withIdentifier: "unwindToAViewController", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// 4
print(segue.identifier)
}
deinit {
// 6
print(String(describing: type(of: self)) + #function)
delegate?.BDelegateFunc(someRandomString: name!)
}
}
/************************************************************/
class AViewController: UIViewController, BDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnClicked(_ sender: Any) {
// 1 MODAL
performSegue(withIdentifier: "presentBFromA", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "presentBFromA" {
// 2
let vc = segue.destination as! BViewController
vc.delegate = self
} else if segue.identifier == "showCFromA" {
// 8 This will finally push C in navigationController
print(segue.identifier)
}
}
#IBAction func unwindToAViewController(segue: UIStoryboardSegue) {
// 5
print(#function)
}
func BDelegateFunc(someRandomString: String) {
// 7
print(someRandomString)
performSegue(withIdentifier: "showCFromA", sender: self)
}
}
Related
There are two ViewController in my app, ViewController and ViewController2
In ViewController, a button set Present Modally segue to "ViewController2"
And ViewController override viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("will appear")
}
In ViewController2, a button to go back
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Now it still can trigger viewWillAppear then I go back to ViewController from ViewController2
If I change ViewController2's presentation from Full Screen to Over Current Context, viewWillAppear will not be triggered
How can I trigger some code when go back?
You can do it without giving up storyboard segues, but you nevertheless had to setup will/did Disappear handler in ViewCOntroller2:
class ViewController: UIViewController {
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? ViewController2 {
(segue.destination as? ViewController2).onViewWillDisappear = {
//Your code
}
}
}
}
class ViewController2: UIViewController {
var onViewWillDisappear: (()->())?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
onViewWillDisappear?()
}
...
}
There are several ways to handle this operation. Here is one, which I used to use.
// ViewController1
class ViewController1: UIViewController {
#IBAction func presentOverCurrentContext(button: Button) {
let vc2 = // instantiate ViewController2
vc2.modalPresentationStyle = .overFullScreen
vc2.presentingVC = self // use this variable 'presentingVC' to connect both view controllers
self.present(vc2, animated: true)
}
}
// ViewController2
class ViewController2: UIViewController {
var presentingVC: UIViewController? // use this variable to connect both view controllers
#IBAction func close(button: Button) {
// handle operation here
presentingVC?.viewWillAppear(true)
self.dismiss(animated: true, completion: {
// or here
// presentingVC?.viewWillAppear(true)
})
}
}
You can also use, your own method to reload view/viewcontroller, but viewWillAppear is common accessible method for all view controllers (as part of super class life cycle) hence you may not need to specify custom type of view controller for presentingVC
While the answers so far provided do work I think it's a good idea to show how to do it using a protocol and delegate as that's a clean implementation which then also allows for further functionality to be added with minimal effort.
So set up a protocol like this:
protocol SecondViewControllerProtocol: class {
func closed(controller: SecondViewController)
}
Setup the second view controller like this:
class SecondViewController {
public weak var delegate: SecondViewControllerProtocol?
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.delegate?.close(controller: self)
}
}
Setup the first view controller like this:
class FirstViewController: SecondViewControllerProtocol {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SecondViewControllerID",
let secondViewController = segue.destination as? SecondViewController {
secondViewController.delegate = self
}
}
func closed(controller: SecondViewController) {
// Any code you want to execute when the second view controller is dismissed
}
}
Implementing it like this does what the original request was and allows for extra methods to be put in the protocol so that the FirstViewController can respond to other actions in the SecondViewController.
Note:
You might want to move the delegate method call into the closure of the dismiss handler so that you know the method is not called until the SecondViewController is actually gone (in case you try to present another view which would fail). If that's the case you could do this:
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true) {
self.delegate?.close(controller: self)
}
}
In fact you could have a will and did methods and call them like this:
#IBAction func close(_ sender: Any) {
self.delegate?.willClose(controller: self)
self.dismiss(animated: true) {
self.delegate?.didClose(controller: self)
}
}
Which would allow you to do something immediately while the second controller is animating away and then know when it has actually gone.
Best/Clean way to handle this scenario to use call back handler.
Example Code
typealias CloseActionHandler = ()-> Void
class TestController: UIViewController {
var closeActionHandler: CloseActionHandler?
func close(_ handler:#escaping CloseActionHandler) {
self.closeActionHandler = handler
}
#IBAction func closeButtonTapped(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.closeActionHandler?()
}
}
class ViewController: UIViewController {
func loadTestController(viewController: TestController) {
viewController.close {
//will be called when user will tap on close button
}
}
}
I have three view controllers like below
I wrote the unwind method in viewcontroller1, and try to receive some data from viewcontroller2 and viewcontroller3 when they unwind to viewcontroller1.
#IBAction func unwindToViewController1(segue: UIStoryboardSegue) {
print("1")
main_content = (segue.source as! MainContentViewController).main_content
}
#IBAction func unwindToViewController2(segue: UIStoryboardSegue) {
print("2")
detailed_content = (segue.source as! SupplementContentViewController).supplement
}
And set the exit unwind segue for both controller 2 and 3 already.
But why the unwindToViewController methods never get called correctly? I think they should be called when I click the button automatically created by the system.
I solve this problem by using delegate instead of unwind. Delegate pattenr is a more explicit way to solve this problem.
By creating a protocol called myDelegate
protocol myDelegate {
func updateMaincontent(main_content : String)
func updateSupplement(supplement: String)
}
And create a delegate instance inside the second view controller
var delegate: myDelegate?
Then in the first view controller, make the class extend myDelegate and set this delegate of second view controller to self in the func prepare(for segue: UIStoryboardSegue, sender: Any?) method
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? MainContentViewController {
destinationViewController.main_content = self.main_content
destinationViewController.delegate = self
}
else if let destinationViewController = segue.destination as? SupplementContentViewController {
destinationViewController.supplement = self.detailed_content
destinationViewController.delegate = self
}
}
Finally, go back to second view controller, and set the value of delegate to what you want in viewWillDisappear method.
func viewWillDisappear(_ animated: Bool) {
self.main_content = contentTextView.text
delegate?.updateMaincontent(main_content: contentTextView.text)
}
I have been trying to perform a segue programmatically by doing:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var btnRedirect: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.performSegueWithIdentifier("segueToSecond", sender: btnRedirect)
}
#IBAction func redirect(sender: AnyObject) {
self.performSegueWithIdentifier("segueToSecond", sender: self)
}
}
The problem is that the segue works perfectly by clicking in the button to perform it although when I try to do it programmatically as you can see inside the viewDidLoad method, it does not work!
I've already tried to pass sender as nil.
As dan'S comment you can't perform segue in ViewDidLoad do it in viewDidAppears.?
Note: Move perform segue from your ViewDidLoad to viewDidAppers or inside your button Action as below code and make sure you set your segue identifier in storyBoard to "segueToSecond".Untested code.So,let me know.if you have any trouble executing.
Updated:
Note: If you want your segue perform automatically after view loads. you should connect segue from VC to VC.
#IBAction func redirect(sender: AnyObject) {
segueToSecondVC()
}
func segueToSecondVC() {
self.performSegue(withIdentifier: "segueToSecond", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "segueToSecond") {
let dest = segue.destination as! viewTwo // viewTwo is your destination ViewController
print("Segue Performed")
}
}
Updated:
Note: If you want the segue perform automatilcally after view loads you can setup some delay to your segue from below code.
let delayInSeconds = 4.0
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
segueToSecondVC()
}
}
Hope this help you to resolve the issue....
I'm presenting a tutorial of 4 UIViewController when my app starts the first time.
Every UIViewController has a Button with a segue presenting the next ViewController.
The last ViewController has a button "Let's start" which should dismiss the tutorial completely.
Problem:
It this dismiss all ViewControllers except the first. I don't understand why?!
What I expect:
On the last ViewController4 I'm calling the dismissIntroduction() function of the first ViewController, so I except ALL ViewControllers (ViewController1 included) should disappear.
When I put a button on the first ViewController and call the function "dismissIntroduction()" it disappears.
ViewController 1 (WelcomeViewController):
protocol WelcomeViewDelegate {
func dismissIntroduction()
}
class WelcomeViewController: UIViewController, WelcomeViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func dismissIntroduction() {
self.dismissViewControllerAnimated(true, completion: nil)
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as! ViewController2
destination.delegate = self
}
}
ViewController 2:
class ViewController2: UIViewController {
var delegate:WelcomeViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as! ViewController3
destination.delegate = self.delegate
}
}
ViewController 3:
class ViewController3: UIViewController {
var delegate:WelcomeViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as! ViewController4
destination.delegate = self.delegate
}
}
ViewController4 (the last one):
class ViewController4: UIViewController {
var delegate:WelcomeViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func pressLetsStart(sender: AnyObject) {
self.delegate!.dismissIntroduction()
}
}
EDIT:
I got it working, when I put the dismissViewControllerAnimated function TWO times!?
func dismissIntroduction() {
self.dismissViewControllerAnimated(true, completion: nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
But why? I don't understand the logic behind...
You are looking at the wrong solution to your problem, what you need to do is look for Unwind Segues. Go through this tutorial: https://spin.atomicobject.com/2014/10/25/ios-unwind-segues/
I solved the problem now with following function in last ViewController:
#IBAction func pressLetsStart(sender: AnyObject) {
self.dismissModalStack()
}
private func dismissModalStack() {
var vc = self.presentingViewController! as UIViewController
while (vc.presentingViewController != nil) {
vc = vc.presentingViewController!;
}
vc.dismissViewControllerAnimated(true, completion: nil)
}
I have an action tied to pop up the MPC view controller however once this action is complected and the browser is dismissed I simply am lead back to my old view controller
func browserViewControllerDidFinish(
browserViewController: MCBrowserViewController!) {
// Called when the browser view controller is dismissed (ie the Done
// button was tapped)
func seguetoJoin(segue: UIStoryboardSegue, sender: AnyObject?) {
var joinView: playMusicViewController = segue.destinationViewController as! playMusicViewController
weak var delegate: MCBrowserViewControllerDelegate!
}
self.dismissViewControllerAnimated(true, completion: nil)
}
You seem to be calling dismissViewController on the VC that is receiving the didFinish callback.
You should move the segue code to the ViewController that needs to perform the segue, rather than performing it on a ViewController that is being dismissed.
Also, these are unused
var joinView: playMusicViewController = segue.destinationViewController as! playMusicViewController
weak var delegate: MCBrowserViewControllerDelegate!
Update -
Following the comments below.
in mpcSubClass.m
var viewControllerToPeformSegue: UIViewController!
func browserViewControllerDidFinish(browserViewController: MCBrowserViewController!) {
// animation:false as your segue will have an animation
self.dismissViewControllerAnimated(false, completion: nil)
self.viewControllerToPerformSegue.handleBrowserDidFinish()
}
in viewControllerToPerformSegue.m
// when you initialise the MPC VC subclass
mpcVCSubclass.viewControllerToPerformSegue = self
func handleBrowserDidFinish()
{
self.performSegueWithIdentifier("mainViewSegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!)
{
if segue.identifier == "mainViewSegue"
{
let mainVC = segue.destination as! MainVC
// now pass any data to your main VC that it can load in viewWillAppear
}
}
in storyboard
link viewControllerToPerformSegue to the MainVC and name the segue mainViewSegue
Hope this helps.