Andrey Maksimkin
Andrey Maksimkin

Reputation: 183

iOS Responder Chain and Events

I'm a little confused. There are two UIViewControllers in UINavagationController's stack. I try to call the method of first UIViewController within the second UIViewController. See code below

class VC1: UIViewController {

    @objc func method1() {
        //not called after tap button
    }
}

class VC2: UIViewController {

    let button = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()

        button.addTarget(nil, action: #selector(VC1.method1), for: .touchUpInside)
    }
}

But method1 isn't called when I tap on the button. What's the problem?

Upvotes: 4

Views: 1076

Answers (3)

mfaani
mfaani

Reputation: 36427

  • When you push, the relationship is between the pushedViewController and navigationController.
  • When you modally present, the relationship is between the presentedViewController and UIWindow. Understandably there is a relationship between the presenting and presented view controllers, but it's just a matter of them having references to one another. Their not hooked up through the responder chain pipeline...
  • Only when a viewController is made a childViewController of anotherViewController then their responder chain is hooked in the way you think meaning the childViewController’s nextResponder becomes its ‘parentViewController’

———

From nextResponder docs:

For example, UIView implements this method and returns the UIViewController object that manages it (if it has one) or its superview (if it doesn’t). UIViewController similarly implements the method and returns its view’s superview. UIWindow returns the application object. The shared UIApplication object normally returns nil, but it returns its app delegate if that object is a subclass of UIResponder and has not already been called to handle the event.

Reading this we understand that neither the presentingViewController nor the pushingViewController are a viewController’s superview

Upvotes: 1

matt
matt

Reputation: 536027

The responder chain is based on superviews and parent view controllers. So VC1 is not up the responder chain from VC2. The responder chain from VC2 leads to its parent (the UINavigationController); it does not go by way of any other children of the UINavigationController.

Upvotes: 4

rob mayoff
rob mayoff

Reputation: 386038

The responder chain, starting from button, goes through button's ancestor views and view controllers. Since VC1 and VC2 are separate items in the navigation controller's stack, VC1 is not an ancestor of VC2. They are more like siblings of each other. So VC1 is not in button's responder chain.

You could just set VC1 as the target of the button action:

let vc1: VC1 = ...
button.addTarget(vc1, action: #selector(VC1.method1), for: .touchUpInside)

But that means VC2 needs reference to the existing VC1 instance. How do you get that reference? It depends on how you created VC2 and pushed it on the navigation stack.

If VC1 creates VC2 in code, then VC1 can just give the new VC2 a reference to self (the VC1) when creating the VC2.

If you have a storyboard segue from VC1 that pushes VC2 onto the stack, then you need to implement prepare(for: UIStoryboardSegue, sender:) in VC1, and in that method you need to hand self (VC1) to the destination of the segue (VC2).

Upvotes: 1

Related Questions