What is the difference between the first and second example?
What is the target responsible for?
In which case is the method called when the button is clicked?
When a button is clicked, our method is called in both cases.
Only in the first example, UIKit will try to call the method in the assigned target (for us this is
ViewController ). It will crash if this method does not exist.
In the second example, the iOS Responder Chain is used,
UIKit will look for the nearest
UIResponder -a which has this method. There will be no crash if our method is not found.
UIViewController, UIView, UIApplication inherit from
iOS Responder Chain and what's under the hood
UIKit iOS Responder Chain process is handled by
UIKit , which dynamically works with a linked list of
UIResponder . This
UIKit created from the first responder (the first
UIResponder that registered the event, we have
UIButton(UIView) and its
UIKit goes through the list of
UIResponder and checks with
canPerformAction for our function.
open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool
If the selected
UIResponder cannot work with a specific method,
UIKit recursively sends actions to the next
UIResponder in the list using the
target method which returns the next
open func target(forAction action: Selector, withSender sender: Any?) -> Any?
This process is repeated until one of the
UIResponder can work with our method or the array ends and the system ignores this event.
In the second example, clicks were processed by the
UIViewController , but
UIKit first sent a request to the
UIView since it was the first responder. He did not have the required method, so
UIKit redirected actions to the next
UIResponder in a linked list who was the
UIViewController that had the desired method.
In most cases, the
iOS Responder Chain is a simple array of
subviews , but its order can be changed. You can make
UIResponder (becomeFirstResponder) become
UIResponder and return it to the old position using
resignFirstResponder . This is often used with a
UITextField to show the keyboard that will be called only when the
UITextField is the
first responder .
iOS Responder Chain and UIEvent
The Responder Chain is also involved in touching the screen, movements, clicks. When the system detects any events (touch, motion, remote-control, press), a
UIEvent is created under the hood and sent using the
UIApplication.shared.sendEvent() method to
UIWindow . After receiving the event,
UIWindow determines using the
hitTest:withEvent to which
UIResponder this event belongs and assigns it the
first responder . Next is the work with a linked list of
UIResponder described above.
To work with system
UIEvent , subclasses of
UIResponder (UIViewController, UIView, UIApplication) can override these methods:
open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) open func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) open func pressesChanged(_ presses: Set<UIPress>, with event: UIPressesEvent?) open func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) open func pressesCancelled(_ presses: Set<UIPress>, with event: UIPressesEvent?) open func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) open func motionCancelled(_ motion: UIEvent.EventSubtype, with event: UIEvent?) open func remoteControlReceived(with event: UIEvent?)
Despite the fact that the ability to inherit and call
sendEvent manually is present,
UIResponder not intended for this. This can create a lot of problems with the work of custom events, which can lead to incomprehensible actions caused by an accidental
first responeder that can respond to your event.
Why it is useful, where to use
Despite the fact that the
iOS Responder Chain fully controlled by
UIKit , it can be used to solve the problem of delegation / communication.
UIResponder action is similar to the one-time
Let's take an example, we have a
UIViewController , which is deep in the UINavigationController stack and we need to tell it what happened when a button was clicked on another screen. You can use the delagate pattern or
NotificationCenter.default.post , but a fairly simple option is to use the
iOS Responder Chain .
button.addTarget(nil, action: #selector(RootVC.doSomething), for: .touchUpInside)
When pressed, the method in the
UIViewController will be called. #selector can take the following parameters:
func doSomething() func doSomething(sender: Any?) func doSomething(sender: Any?, event: UIEvent?)
sender is the object that sent the event - UIButton, UITextField, and so on.
Additional Resources for Learning [eng]:
Good description of UIEvent, UIResponder and a couple of advanced examples (patern coordinator)
Read more about ios responder chain
Practical example of a responder chain
Off dock on iOS responder chain
Off Dock by UIResponder