DEV Community

GoyesDev
GoyesDev

Posted on

Responder Chain

Dos clases conectadas directamente pueden comunicarse a través del patrón "Delegate". Si tenemos varios niveles de indirección, usar el patrón "Delegate" puede ser muy enredado. Una alternativa es el uso del "Responder Chain" de Apple.

El "responder chain" es una serie de objetos de tipo UIResponder que tratan de procesar un evento. Si no pueden hacerlo, lo mandan al siguiente elemento de la cadena para que trate de procesarlo.

El primer objeto que recibe el evento se denomina "First Responder". Si este no puede manejar el evento, se lo mandará al siguiente objeto de la cadena llamado "Next Responder". La cadena se propaga en este orden:

  1. El componente que recibe el evento (UILabel, UITextField, UIButton),
  2. La vista (UIView) que contiene el componente.
  3. El UIViewController que contiene la vista.
  4. La ventana (UIWindow) que contiene al UIViewController.
  5. La aplicación (UIApplication) que contiene la ventana.
  6. El delegado de la aplicación (UIApplicationDelegate).

Para usar este mecanismo de comunicación, nos aprovechamos del patrón target-action, sobre UIApplication.shared, pasando nil como target y self como punto de partida de la búsqueda (En caso de no tener un punto de partida y querer examinar todo, se puede pasar nil):

UIApplication.shared
  .sendAction(#selector(ResponderAction.someAction), 
              to: nil, // Este es el target = nil
              from: self, 
              for: nil)
Enter fullscreen mode Exit fullscreen mode

Este mensaje será recibido por el primer elemento de la cadena de UIResponder que tenga definido el método ResponderAction.someAction. Por ejemplo:

extension MyViewController: ResponderAction {
  @objc func someAction(_ sender: Any?) { 
    print("Recibí el mensaje en VC")
  }
}
Enter fullscreen mode Exit fullscreen mode
extension UIWindow: ResponderAction {
  @objc func someAction(_ sender: Any?) { 
    print("Recibí el mensaje en Window")
  }
}
Enter fullscreen mode Exit fullscreen mode
extension UIApplication: ResponderAction {
  @objc func someAction(_ sender: Any?) { 
    print("Recibí el mensaje en UIApplication")
  }
}
Enter fullscreen mode Exit fullscreen mode
extension AppDelegate: ResponderAction {
  @objc func someAction(_ sender: Any?) { 
    print("Recibí el mensaje en AppDelegate")
  }
}
Enter fullscreen mode Exit fullscreen mode

Tener en cuenta que el evento será procesado por el primer objeto de tipo UIResponder que reciba el mensaje. Si un objeto lo recibe, los demás objetos de la cadena no harán nada.

Para depurar un UIResponder se puede buscar el atributo next. Cuando se llegue al UIApplicationDelegate, no habrá más next.

¡Atención! Los eventos de CoreMotion (accelerometers, gyroscopes, etc) no se reciben a través del responder chain.


Determinar el "First Responder"

En ocasiones puede ser necesario tener una referencia al "first responder". No existe ningún mecanismo nativo para hacerlo, sin embargo, se puede conseguir por medio del "responder-chain" con el siguiente bloque de código:

import UIKit
extension UIResponder {
  private static weak var firstResponder: UIResponder?
  static func currentFirst() -> UIResponder? {
    firstResponder = nil
    UIApplication.shared.sendAction(#selector(currentFirstListener), to: nil, from: nil, for: nil)
    return firstResponder
  }
  @objc private func currentFirstListener() {
    UIResponder.firstResponder = self
  }
}
Enter fullscreen mode Exit fullscreen mode

Observaciones

Este mecanismo no es muy conocido, así que es mejor no usarlo. En lo posible usar el patrón "Delegate".

Top comments (0)