47

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)}
} 
es3dev
  • 471
  • 1
  • 4
  • 4

5 Answers5

84

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
  • 22,409
  • 18
  • 87
  • 132
  • so if this is the case then my segue can't be set from the UIButton because even if I do nothing in that IBAction the segue will be performed. Instead the segue should just be from the view controller...correct? – es3dev Nov 15 '14 at 17:33
  • 2
    This is so obvious once you know the answer...and now thanks to you I know the answer!! Thanks! – Scooter Jun 03 '17 at 01:07
34

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

tanz
  • 2,557
  • 20
  • 31
  • This is a pragmatic solution. – josefdlange Jun 24 '15 at 23:11
  • Prefect answer. And a must-have fix for all VCs. I will put into my BaseVC so all VC can benefits. In fact, using the "shouldPerformSegueWithIdentifier", number of lines are shorter and segues can be both via programming and storyboard in the same VC – John Pang Dec 15 '15 at 17:34
3

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

user2722667
  • 8,195
  • 14
  • 52
  • 100
3

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)
}
Vergiliy
  • 1,248
  • 1
  • 13
  • 22
0

if you're using this code you need to remove;

[self performSegueWithIdentifier:name sender:sender];

ali ozkara
  • 5,425
  • 2
  • 27
  • 24