Blog

Understanding the 'sender' in segues and use it to pass on data to another view controller

08 May, 2015

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.
Screen Shot 2015-05-08 at 23.25.37
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 https://github.com/lammertw/SegueColorSample.

guest
12 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
MCegiela
MCegiela
6 years ago

This is an anti-pattern. You should handle dependency injection through making your VC ‘prepared for the segue’ and aware of the context.

lazar
lazar
6 years ago
Reply to  MCegiela

WHY is this anti-pattern? Is your proposal “pro”-pattern? Why should my VC be aware of the context, what kind of context? Please elaborate on that.

Carmen
Carmen
6 years ago

Oh no don’t do this. this not good programming style, as MCegiela said it’s an anti pattern

Carl Hunter Roach
Carl Hunter Roach
6 years ago

thanks for the article.
do you know how one could add state restoration to this example?
The issue is: how to set colorViewController.color when prepareForSegue() isn’t called?

Darren
Darren
6 years ago

What is the name of the anti-pattern that this is supposed to be? I was just about to do this myself. It feels funny, but I can’t actually see what’s wrong with it…

Marius
Marius
6 years ago
Reply to  Darren

Anti-patterns generally don’t have names. An anti-pattern is something that is commonly done that wrong way and that may cause unexpected problems in your code. For example by using stuff for things they aren’t supposed to…

lazar
lazar
6 years ago
Reply to  Darren

Its not an anti-pattern. Its the design flaw of apple so a solution is a matter of compromise. For me its even worse to use “conventional”approach where we introduce state to the source VC just for the sake of passing the data.

Dust
Dust
6 years ago

Nice post,
now i would like to see remotenotification load your app to a custom view!

Chris
Chris
6 years ago

Thanks for this. Really helpful.
Could you please also give an example using shouldPerformSegueWithIdentifier(_:sender:).
I want to use this for a login but not sure how to use.

Explore related posts