iOS Responder Chain or What they ask at the interview

image







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?


TL; DR



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



.







iOS Responder Chain and what's under the hood



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



.







image







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



.







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 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.







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








All Articles