One of the downsides of using segues in storyboards is that you often still need to write code to pass on data from the source view controller to the destination view controller. The prepareForSegue(:sender:) method is the right place to do this. Sometimes you need to manually trigger a segue by calling performSegueWithIdentifier(:sender:), and it’s there you usually know what data you need to pass on. How can we avoid adding extra state variables in our source view controller just for passing on data? A simple trick is to use the sender parameter that both methods have.
Update:
Please keep in mind that this is considered an anti-pattern as commented by some readers below, this post is meant to explore the possibilities of segues, which doesn’t mean you should always use it. Use it only when appropriate or when no other option is available.
The sender parameter is normally used by storyboards to indicate the UI element that triggered the segue, for example an UIButton when pressed or an UITableViewCell that triggers the segue by selecting it. This allows you to determine what triggered the segue in prepareForSegue:sender:, and based on that (and of course the segue identifier) take some actions and configure the destination view controller, or even determine that it shouldn’t perform the segue at all by returning false in shouldPerformSegueWithIdentifier(:sender:).
When it’s not possible to trigger the segue from a UI element in the Storyboard, you need to use performSegueWithIdentifier(:sender:) instead to manually trigger it. This might happen when no direct user interaction should trigger the action of some control that was created in code. Maybe you want to execute some additional logic when pressing a button and after that perform the segue. Whatever the situation is, you can use the sender argument to your benefit. You can pass in whatever you may need in prepareForSegue(:sender:) or shouldPerformSegueWithIdentifier(:sender:).
Let’s have a look at some examples.
Here we have two very simple view controllers. The first has three buttons for different colors. When tapping on any of the buttons, the name of the selected color will be put on a label and it will push the second view controller. The pushed view controller will set its background color to the color represented by the tapped button. To do that, we need to pass on a UIColor object to the target view controller.
Even though this could be handled by creating 3 distinct segues from the buttons directly to the destination view controller, we chose to handle the button tap ourselves and the trigger the segue manually.
You might come up with something like the following code to accomplish this:
[objc]
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
var tappedColor: UIColor?
@IBAction func tappedRed(sender: AnyObject) {
label.text = "Tapped Red"
tappedColor = UIColor.redColor()
performSegueWithIdentifier("ShowColor", sender: sender)
}
@IBAction func tappedGreen(sender: AnyObject) {
label.text = "Tapped Green"
tappedColor = UIColor.greenColor()
performSegueWithIdentifier("ShowColor", sender: sender)
}
@IBAction func tappedBlue(sender: AnyObject) {
label.text = "Tapped Blue"
tappedColor = UIColor.blueColor()
performSegueWithIdentifier("ShowColor", sender: sender)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowColor" {
if let colorViewController = segue.destinationViewController as? ColorViewController {
colorViewController.color = tappedColor
}
}
}
}
class ColorViewController: UIViewController {
var color: UIColor?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = color
}
}
[/objc]
We created a state variable called tappedColor to keep track of the color that needs to be passed on. It is set in each of the action methods before calling performSegueWithIdentifier("ShowColor", sender: sender) and then read again in prepareForSegue(_:sender:) so we can pass it on to the destination view controller.
The action methods will have the tapped UIButtons set as the sender argument, and since that’s the actual element that initiated the action, it makes sense to set that as the sender when performing the segue. So that’s what we do in the above code. But since we don’t actually use the sender when preparing the segue, we might as well pass on the color directly instead. Here is a new version of the ViewController that does exactly that:
[objc]
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
@IBAction func tappedRed(sender: AnyObject) {
label.text = "Tapped Red"
performSegueWithIdentifier("ShowColor", sender: UIColor.redColor())
}
@IBAction func tappedGreen(sender: AnyObject) {
label.text = "Tapped Green"
performSegueWithIdentifier("ShowColor", sender: UIColor.greenColor())
}
@IBAction func tappedBlue(sender: AnyObject) {
label.text = "Tapped Blue"
performSegueWithIdentifier("ShowColor", sender: UIColor.blueColor())
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowColor" {
if let colorViewController = segue.destinationViewController as? ColorViewController {
colorViewController.color = sender as? UIColor
}
}
}
}
[/objc]
This allows us to get rid of our extra tappedColor variable.
It might seem to (and perhaps it does) abuse the sender parameter though, so use it with care and only where appropriate. Be aware of the consequences; if some other code or some element in a Storyboard triggers the same segue (i.e. with the same identifier), then the sender might just be an UI element instead of the object you expected, which will lead to unexpected results and perhaps even crashes when you force cast the sender to something it’s not.
You can find the sample code in the form of an Xcode project on Github SegueColorSample.