Using The Responder Chain

The delegate pattern is often used by a child view controller to communicate with its parent view controller, but it’s not the only way. A lesser used, but occasionally useful, alternate approach is the responder chain.

Responder Chain

I’ve never given much thought to the responder chain. It’s always been there, working its magic, but I generally only interact with it when resigning the first responder to dismiss the keyboard.

You are probably familiar with connecting a control, such as a button, to an action method in a view controller. You can do this with Interface Builder or by adding a target-action in code. For example, in a view controller that should handle the button event (.touchUpInside):

button.addTarget(self, action: #selector(okAction(_:)),
                          for: .touchUpInside)

The target is the view controller (self), and the selector identifies the action in the view controller:

@objc func okAction(_ sender: UIButton) { ... }

When the button wants to send the action it calls the sendAction method of the application instance:

UIApplication.shared.sendAction(
  #selector(okAction(_:)),
  to: viewController, from: self, for: event)

Nothing is stopping us from using the same approach for our actions. Even better if we use nil for the target, we allow UIKit to walk its way up the responder chain looking for an object that responds to our action.

An Example

Let’s see an example. I have been playing with container view controllers recently, and I ended up with this view hierarchy:

The list view controller and results view controller are both instances of a table view controller subclass. When the user selects an object in the table view, I want my root view controller to present that object in the detail view controller of a split view.

My first choice would be to define a delegate protocol for the table view controller and make the root view controller the delegate. That’s possible, but a bit cumbersome in this case. I also didn’t want to chain several intermediate delegates to pass the actions back up to the root. Let’s see how it looks if we use the responder chain instead of delegation.

Sending The Action

First, we need to define the action(s) we want to send. A protocol does the job:

@objc protocol ListAction: AnyObject {
  func didSelectItem(_ sender: ListViewController)
}

I only need one action in this case but note I marked the protocol as @objc to make it visible to the Objective-C runtime.

Then in my table view controller, I need to send the action to the responder chain when the user selects a row:

override func tableView(_: UITableView, didSelectRowAt _: IndexPath) {
  UIApplication.shared.sendAction(
     #selector(ListAction.didSelectItem(_:)), 
     to: nil, from: self, for: nil)
}

Notes:

  • The first argument is the selector for the action we want to send to the target.
  • The second argument is the target of the action. Using nil sends the action to the first responder and then up the responder chain until we find something to handle it.

Responding To The Action

To have our root view controller handle the action we need to add the required method:

extension RootViewController: ListAction {
  func didSelectItem(_ sender: ListViewController) {
    if let object = sender.selectedObject {
      // present the detail view controller
    }
}

Too Magical?

I still prefer the direct approach of a delegate when possible. I find it easier to understand and debug when it doesn’t work. For situations where you can’t easily use a delegate, or you want something more flexible, the responder chain is an option worth knowing about.

Further Reading

I found this post helpful when understanding the responder chain:

For a recap on using delegates with Swift: