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 UIResponder
.
The UIKit
iOS Responder Chain process is handled by UIKit
, which dynamically works with a linked list of UIResponder
. This UIKit
list UIKit
created from the first responder (the first UIResponder
that registered the event, we have UIButton(UIView)
and its subviews
.
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 UIResponder
.
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
first 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
.
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.
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 NotificationCenter.default.post
.
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.
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