Using dismissViewController on queue of multiple UIViewControllers with delegate - ios

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

Related

Can't pass values in UIStoryBoard segue [duplicate]

This question already has answers here:
Swift pass data through navigation controller
(8 answers)
Closed 4 years ago.
I need to send bool value to Second viewController based on some condition.
but in Second ViewController the value of bool variable is always false.
This is First ViewController code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func moveTo(_ sender: Any) {
self.performSegue(withIdentifier: "Move2", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "Move2"
{
let secondVC = segue.destination as? SecondViewController
secondVC?.second = true
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The SecondViewController code is
import UIKit
class SecondViewController: UIViewController {
var second = Bool()
override func viewDidLoad() {
super.viewDidLoad()
print(second)
// Do any additional setup after loading the view.
}
But this Value of second variable is false.
Whats wrong in my code. need help. TIA..
EDIT : In both ViewController i have navigation controller. That's problem. SecondVC is SecondViewController's navigation controller, so that i can't pass data to that secondVC.
Go to your Storyboard and select SecondViewController (the yellow circle on the top left. Go to identity inspector and select SecondViewController from the Class drop down.
See image below:
Keep things simple and clean :
FirstVC :
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "Move2"
{
let secondVC = segue.destination as? SecondViewController
secondVC.second = true
}
}
SecondVC :
class SecondViewController: UIViewController {
var second: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
print(second)
}
}
Just tested this myself. true is printed.

How to trigger some code when back from "Over Current Content" ViewController?

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
}
}
}

Can't get my Delegate protocol to work when dealing with a Container VC?

I cannot get my delegate protocol to work. I used this stack overflow questions as a guide dispatch event to parent ViewController in swift . I don't know if things have changed in Swift 3 since this post, but my function in my parentViewController is never getting called. Here is my setup.
//PROTOCOL
protocol PDPPropDetailsDelegate {
func buttonPressed(PropDetailsVC: propertyDetailsVC)
}
// CHILD VIEW CONTROLLER
class propertyDetailsVC: UIViewController {
var delegate: PDPPropDetailsDelegate?
#IBAction func emailButton(_ sender: AnyObject) {
self.delegate?.buttonPressed(PropDetailsVC: self)
}
}
The Button is getting called in Child View Controller.
// PARENT VIEW CONTROLLER
class ImageDetailsVC: UIViewController, PDPPropDetailsDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "container"{
container = segue.destination as! ContainerViewController
}
}
#IBAction func segmentControlAct(_ sender: Any) {
switch segmentControllerView.selectedIndex {
case 0: print("case 1")
container!.segueIdentifierReceivedFromParent("first")
case 1: print("case 2")
container!.segueIdentifierReceivedFromParent("second")
PropertyDetailsVC.delegate = self // **WHERE I SET DELEGATE**
setUpPropertyDetailsUI(property: filterImages)
default: print("default")
}
}
func buttonPressed(PropDetailsVC: propertyDetailsVC) {
print("BUTTON PRESSED")
}
}
Button Pressed is never called. I assume it has to do with the delegate not getting set properly. Not exactly sure why that would be the case though. My setUpPropertyDetailsUI(property: filterImages) takes the Outlets from that VC and sets that works just fine. I did a breakpoint and it is called when I segment over to the PropertyDetailsVC. Any advice or suggestions?
import UIKit
open class ContainerViewController: UIViewController {
//Manipulating container views
fileprivate weak var viewController : UIViewController!
//Keeping track of containerViews
fileprivate var containerViewObjects = Dictionary<String,UIViewController>()
/** Specifies which ever container view is on the front */
open var currentViewController : UIViewController{
get {
return self.viewController
}
}
fileprivate var segueIdentifier : String!
/*Identifier For First Container SubView*/
#IBInspectable internal var firstLinkedSubView : String!
override open func viewDidLoad() {
super.viewDidLoad()
}
open override func viewDidAppear(_ animated: Bool) {
if let identifier = firstLinkedSubView{
segueIdentifierReceivedFromParent(identifier)
}
}
override open func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func segueIdentifierReceivedFromParent(_ identifier: String){
self.segueIdentifier = identifier
self.performSegue(withIdentifier: self.segueIdentifier, sender: nil)
}
override open func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == segueIdentifier{
//Remove Container View
if viewController != nil{
viewController.view.removeFromSuperview()
viewController = nil
}
//Add to dictionary if isn't already there
if ((self.containerViewObjects[self.segueIdentifier] == nil)){
viewController = segue.destination
self.containerViewObjects[self.segueIdentifier] = viewController
}else{
for (key, value) in self.containerViewObjects{
if key == self.segueIdentifier{
viewController = value
}
}
}
self.addChildViewController(viewController)
viewController.view.frame = CGRect(x: 0,y: 0, width: self.view.frame.width,height: self.view.frame.height)
self.view.addSubview(viewController.view)
viewController.didMove(toParentViewController: self)
}
}
}
import UIKit
class EmptySegue: UIStoryboardSegue{
override func perform() {
}
/*
// 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?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
You seem to be a little confused at the flow of the app. Here's an answer I wrote for someone else's question about this same topic:
https://stackoverflow.com/a/45312362/3832646
Your protocol and Child view controller look great, but there are quite a few things amiss with the rest of your code here:
your prepare(for segue:_, sender:_) is typically where you would set the delegate for the destination (child) view controller.
PropertyDetailsVC.delegate = self won't do anything - you need an instance of the view controller to set its delegate.
It looks like you're using some sort of container global variable that I'm not sure what it would be for.
Take a look at the answer I posted and have another go. It's in Swift 3.

Pass data backward from detailViewController to masterViewController

I am trying to pass data back from the second viewController.
I can do that without NavigationController. But now I need to use NavigationController. Then my code does work as before. The data wont pass.
Here is the simple code:
In first viewController
class ViewController: UIViewController, backfromSecond {
#IBOutlet weak var text: UILabel!
var string : String?
override func viewDidLoad() {
super.viewDidLoad()
self.string = "Start here"
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.text.text = self.string
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? secondViewController{
destinationViewController.delegate = self
}
}
func back(text: String) {
self.string = text
print(text)
}
}
And Second viewController:
protocol backfromSecond {
func back(text: String)
}
class secondViewController: UIViewController {
var string : String = "nothing here"
var delegate : backfromSecond?
override func viewDidLoad() {
super.viewDidLoad()
delegate?.back(text: string)
// Do any additional setup after loading the view.
}
}
What is wrong here?
Suppose A & B are two controllers and you first navigated from A to B with some data. And now you want to POP from B to A with some data.
Unwind Segues is the best and recommended way to do this.
Here are the steps.
Open A.m
define following method
#IBAction func unwindSegueFromBtoA(segue: UIStoryNoardSegue) {
}
open storyboard
Select B ViewController and click on ViewController outlet. press control key and drag to 'Exit' outlet and leave mouse here. In below image, selected icon is ViewController outlet and the last one with Exit sign is Exit Outlet.
You will see 'unwindSegueFromBtoA' method in a popup . Select this method .
Now you will see a segue in your view controler hierarchy in left side. You will see your created segue near StoryBoard Entry Piont in following Image.
Select this and set an identifier to it. (suggest to set the same name as method - unwindSegueFromBtoA)
Open B.m . Now, wherever you want to pop to A. use
self.performSegueWithIdentifier("unwindSegueFromBtoA", sender: dataToSend)
Now when you will pop to 'A', 'unwindSegueFromBtoA' method will be called. In unwindSegueFromBtoA of 'A' you can access any object of 'B'.
That's it..!
I think your problem is in the prepare for segue method. If the view controller is on a navigation stack i think your code should be something like
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? UINavigationController).topViewController as! secondViewController{
destinationViewController.delegate = self
}
}
You can use unwind segues to pass data back.
Here's a tutorial
https://spin.atomicobject.com/2014/10/25/ios-unwind-segues/
This works me well.
1st VC
class ViewController: UIViewController, backfromSecond {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func Passingfrom1stVCTo2ndVC(_ sender: AnyObject) {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController3") as? ViewController3{
vc.dataFrom1StVC = "message send from 1st VC"
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
}
func back(text: String) {
print("data\(text)")
}
}
2nd VC.
protocol backfromSecond: class {
func back(text: String)
}
class ViewController3: UIViewController {
var dataFrom1StVC : String? = nil
week var delegate : backfromSecond?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func DataSendFrom2ndVCTo1stVC(_ sender: AnyObject) {
self.delegate?.back(text: "Message Send From 2nd vc to 1st VC")
self.navigationController?.popViewController(animated: true)
}
}
I hope it will work you. If any problem then ask me i will help you.

Segue programmatically does not work in viewDidLoad

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

Resources