-targetViewControllerForAction:sender: is smarter than it seems

The new -[UIViewController targetViewControllerForAction:sender:] method (and its related methods, -showViewController:sender: and -showDetailViewController:sender:) in iOS 8 takes a clever responder chain-based approach to showing view controllers in a size-class-adaptable way.

Basically, all UIViewController instances respond to -showViewController:sender: and -showDetailViewController:sender:, but their implementations use -targetViewControllerForAction:sender: to find the appropriate view controller to actually do the showing. In the case of -showViewController:sender:, this is likely an enclosing UINavigationController, and in the case of -showDetailViewController:sender:, this is likely an enclosing UISplitViewController.

But according to the documentation, this shouldn’t actually work. -targetViewControllerForAction:sender: is documented to use -canPerformAction:withSender: to determine an eligible target. That method, in turn, is documented to return YES if the receiver responds to the given selector.

If that’s all true, then -targetViewControllerForAction:sender: should always return self when given @selector(showDetailViewController:sender:)! But it clearly doesn’t—it returns an enclosing UISplitViewController, if one exists. So what’s going on?

Well, the docs for -targetViewControllerForAction:sender: lie. -targetViewControllerForAction:sender: does indeed walk the view controller hierarchy, sending -canPerformAction:withSender: on the way. But if a view controller returns YES, it will then determine whether the instance’s method for that selector is an override of a UIViewController implementation for the same selector. If not, it keeps looking up the chain.

This is why UISplitViewController is able to ensure it is returned for -targetViewControllerForAction:@selector(showDetailViewController:sender:) instead of any intermediate view controllers between itself and the sender. It’s also why applications are able to mimic UIKit’s pattern with their own custom actions, which they wouldn’t if UIViewController instead relied on a hardcoded list of selectors.

I’ve filed documentation feedback with Apple. But in the meantime, remember that -targetViewControllerForAction:sender: is smarter than it seems!