Calling performSeguewithIdentifier doesn't call shouldperformseguewithIdentifier - ios

I have two view controllers. On view controller1 I have the following:
a segue that takes me to viewcontroller2 - this segue is named "showme" and is attached to the viewcontroller
an IBAction for a UIButton
In my code I have the following for the button press action
#IBAction func buttonPress(sender: AnyObject) {
println("button pressed")
performSegueWithIdentifier("showme", sender: self)
}
I also have the following method:
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
println("Should performing....")
return true
}
For some reason the shouldPerformSegueWithIdentifier function is never called. If however, I add the segue directly on the UIButton to ViewController2 it is.
I have confirmed that calling it direction within my button action works (see below), but this is not what I understand to be the way it works. The same is true for prepareforSegue..
#IBAction func buttonPress(sender: AnyObject) {
println("button pressed")
if (shouldPerformSegueWithIdentifier("showme", sender: self)){
performSegueWithIdentifier("showme", sender: self)}
}

This behaviour is perfectly natural, for the following reasons:
1) shouldPerformSegueWithIdentifier is used to make sure that a segue that has been set up in Storyboards should be triggered, so it only gets called in the case of Storyboard Segues and gives you the chance to not actually perform the segue.
2) When you call performSegueWithIdentifier yourself, shouldPerformSegueWithIdentifier is not called because it can be assumed that you know what you are doing. There would be no point in calling performSegueWithIdentifier but then return a NO from shouldPerformSegueWithIdentifier.

#nburk answer is absolutely correct.
However I understand that in some situations it could be useful if shouldPerformSegueWithIdentifier:sender: would be called anyway, also when a call to performSegueWithIdentifier:sender: is made in code.
For instance we want to make some validations to decide whether performing a segue or not and we want to keep this logic in a single place and not duplicating all over the place conditions like the following:
if (self.shouldPerformSegue) {
[self performSegueWithIdentifier:identifier sender:sender];
}
This can be easily achieved overriding performSegueWithIdentifier:sender: as follows:
- (void)performSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
if ([self shouldPerformSegueWithIdentifier:identifier sender:sender]) {
[super performSegueWithIdentifier:identifier sender:sender];
}
// otherwise do nothing
}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
return self.shouldPerformSegue;
}
This way you can use shouldPerformSegueWithIdentifier:sender: to define your logic to allow/deny both IB and code triggered segues.

As the answer above. If you call performSegueWithIdentifier then shouldPerformSegueWithIdentifier is not called.
As an example:
Lets say you have an embedded segue inside a container view in order to show some images that you can swipe through. And embedded segues gets fired right away when you VC has loaded. But if you would have to download the images from an remote API your app would crash since there wouldnt be any images to display in the embedded segue/container view.
In this case shouldPerformSegueWithIdentifier would be needed.
You could setup a boolean value that you check in shouldPerformSegueWithIdentifier if its false return false and your segue wont be fired. And once your app has downloaded the images you could call performSegueWithIdentifier

Thanks #tanzolone for the perfect solution. Rewrote code on Swift 5.
To forcefully call shouldPerformSegue before performingSegue, you can override performingSegue in you class:
override func performSegue(withIdentifier identifier: String, sender: Any?) {
if shouldPerformSegue(withIdentifier: identifier, sender: sender) {
super.performSegue(withIdentifier: identifier, sender: sender)
}
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
// Your code (return true if you want to perform the segue)
}

if you're using this code you need to remove;
[self performSegueWithIdentifier:name sender:sender];

Related

performSegue creates two view controllers

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
self.delegate = segue.destination as? MenuViewController
print("check_1", self.delegate)
}
#IBAction func openMenu(_ sender: Any) {
performSegue(withIdentifier: "openMenu", sender: sender)
print("check_2", self.delegate)
}
My main ViewController updates values while MenuViewController displays these values. Each time ViewController values are updated, it calls self.delegate.updateValues in MenuViewController. I transition between the two ViewControllers through buttons.
My problem is that it seems like the MenuViewController displayed is a different object than the one stored in self.delegate inside ViewController. Printing the check statements:
check_1 Optional(<Menu.MenuViewController: 0x10161ca10>)
check_2 Optional(<Menu.MenuViewController: 0x10161ca10>)
check_1 Optional(<Menu.MenuViewController: 0x10161dd10>)
May I ask how do I make sure only one instance of MenuViewController is created and stored in self.delegate?
When you add a segue to a storyboard, if you hook up the segue to a specific button/IBAction, you don't need to call performSegue manually, it will be automatically called for you.
You have 2 segues executed, since both the storyboard executes the segue and then you also do it from code by calling performSegue.
performSegue should only be used when your segue isn't directly hooked up to a UI event or if you need to conditionally perform a segue - such as when you have a login button, where depending on the network response, you might execute an error or a login segue.

Avoid performSegue multiple times

I have more than 20+ buttons in my application. For these buttons if I click twice it performSegue twice and opens the viewcontroller twice, throughout my application in swift iOS?
self.performSegue(withIdentifier: "toViewController", sender: nil)
You can attach a tag to each button and define an array of segues
self.performSegue(withIdentifier:segues[sender.tag], sender: nil)
or simply make the button the source of the segue , if you're not willing to override prepareForSegue
You could declare a property
var isSegueEnabled = true
Then implement the method to control whether the segue should be performed
func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
return isSegueEnabled
}
In prepare(for segue set the property to false
isSegueEnabled = false
At some point after the presented view controller has been dismissed set isSegueEnabled back to true

segue storyboard condition issue

I have this two controller liked by segue:
My problem is that:
when I tap on selected table view'cell in general I have to follow the segue and so go to other controller , but if a specific condition is true I have to show ad alert view and so I don't follow the segue and so don't go to those controller.
In this way when I tap on selected cell I go always on the other controller.
How do I solve this problem?
if you override this function segue wont continue if you return false, this gives you the oportunity to show a warning under certain conditions, after that you can performSegueWithIdentifier("segueidentifier", sender: self), and your good to go.
override func shouldPerformSegueWithIdentifier(identifier: String!, sender: AnyObject?) -> Bool {
if identifier == "segueidentifier" {
//show warning and perform segue with this identifier on the accept button listener.
return false
}
}
return true
}
You can add a segue from one controller to another without specifying exact view which triggers the segue. Then just check your condition in didSelectRow method and call performSegue method if you need to show new view controller:
if condition {
performSegue(withIdentifier: "MySegueIdentifier", sender: nil)
}
else {
//show ad here
}
To add a segue between controllers just drag with right mouse button from one controller to another and pick "Show".
Then select segue and add a string identifier for it:
Make sure you use the same string in the code when you call performSegue method.

Swift: Creating the segue between two controllers does not accept my second view controller

I am trying to segue between two uiviewcontrollers but Swift will not compile my code. Here is my code:
prepareForSegue(segue: UIStoryboardSegue(identifier: "containerSegue", source: self, destination: EventColumnViewController.self), sender: nil)
Where EventColumnViewController is my custom ViewController class, and here is the error I get:
Cannot find an initializer for type 'UIStoryboardSegue' that accepts an argument list of type '(identifier: String, source: ViewController, destination: EventColumnViewController.Type)'
Why is this happening? How can I fix this?
Here is my class declaration: class EventColumnViewController: UIViewController, UIViewControllerTransitioningDelegate
You don't want to specify a specific segue in the function signature for prepareForSegue. Instead the function receives whichever segue is triggered and you can perform specific code for a certain segue using if blocks.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "containerSegue") {
// perform specific code here.
}
}
If you actually want to perform the specific segue named "containerSegue" programatically, you would want to use a different function:
performSegueWithIdentifier("containerSegue", sender: self)
Notice the difference:
prepareForSegue is used to do setup (such as passing data to the next controller) after a segue has been triggered
performSegueWithIdentifier actually triggers the segue

Modifing one variable from another view controller swift

I am developing an app in Swift that, in gist, tells people the price of Bitcoin in various currencies. To select the currency, the user chooses from a list in a view controller with a UITableView. This is currencyViewController, and it is presented from my main screen, viewController.
What I want to happen is that, when the user dismisses currencyViewController, it passes a string to a UIButton in the main viewController.
Here's the prepareForSegue function that should pass the data:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "presentCurrency") {
currencySelector.setTitle("\currencySelected", forState: UIControlState.Normal)
}
}
CurrencySelector is a UIButton in the main viewController, and currencySelected is a variable in the second view controller, currencyViewController.
It gives the error "Invalid Escape Sequence In Literal"
So, I've narrowed it down to one of two issues:
The variables from viewController can't be "seen" from currencyViewController. If so, how can I modify the text of CurrencySelector from CurrencyViewController?
For some reason, when the user exits the pushed CurrencyViewControler, prepareForSegue isn't called.
What is going on here? Thanks, and apologies - I am but a swift newbie.
2 - "prepareForSegue" is called when you push a new view controller via the segue, but not when you dismiss it. No segue is called upon dismissal.
1 - A good way to do this would be the delegate pattern.
So the main view controller would be the delegate for the currencyViewController, and would receive a message when that controller is dismissed.
In the start of the currencyViewController file you prepare the delegate:
protocol CurrencyViewControllerDelegate {
func currencyViewControllerDidSelect(value: String)
}
and you add a variable to the currencyViewController:
var delegate : CurrencyViewControllerDelegate?
Now, the mainViewController has to conform to that protocol and answer to that function:
class MainViewController : UIViewController, CurrencyViewControllerDelegate {
//...
func currencyViewControllerDidSelect(value: String) {
//do your stuff here
}
}
And everything is prepared. Last steps, in prepareForSegue (MainViewController), you will set the delegate of the currencyViewController:
var currencyVC = segue.destinationViewController as CurrencyViewController
currencyVC.delegate = self;
And when the user selects the value in currencyViewController, just call that function in the delegate:
self.delegate?.currencyViewControllerDidSelect("stuff")
A bit complex maybe, but it's a very useful pattern :) here is a nice tutorial with more info if you want it:
http://www.raywenderlich.com/75289/swift-tutorial-part-3-tuples-protocols-delegates-table-views
You have to use the parantheses to eval variables in strings, i.e. println("\(currencySelected)")
To access variables in the second view controller (the one which is the destination of the segue) you have to get a reference to it:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "presentCurrency") {
let currencyViewController = segue.destinationViewController as CurrencyViewController // the name or your class here
currencySelector.setTitle("\(currencyViewController.currencySelected)", forState: UIControlState.Normal)
}
}

Resources