Skip to content

Latest commit

 

History

History
132 lines (95 loc) · 3.95 KB

Responder-Chain.md

File metadata and controls

132 lines (95 loc) · 3.95 KB

Responder Chain

How to fire manually

@objc protocol ActivationTileActions {
    func performSetupYourModemAction(sender: Any?)
}

extension ActivationTileViewController: ActivationTileRowButtonViewDelegate {

    func didPressButton(_ sender: Any?) {
        UIApplication.shared.sendAction(#selector(ActivationTileActions.performSetupYourModemAction), to: nil, from: self, for: nil)
    }
}

An Example

TableView

import UIKit

/*
 The ResponderChain fires UIEvents up the Responder chain stack giving each UIResponder
 a chance to handle.

 The subtile difference in target is whether you set yourself, or `nil`.

 If you set `self` as the target, you will get a chance to intercept the responder chain call, and either
 handle it or refire it up the chain again with `UIApplication.shared.sendAction`.

    > button.addTarget(self, action: #selector(responderPressed(sender:)), for: .primaryActionTriggered)

 If you set the target as `nil` initially, the event will continue to walk up the chain, calling `next`, one
 every responder it meets, until it finds one that satisfier the action.

    > button.addTarget(nil, action: .performFetchWeatherAction, for: .primaryActionTriggered)

 */
class ResponderChainViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
    }

    let sharedActionButton: UIButton = {
        let button = makeButton(withText: "UIApplication.shared")
        button.addTarget(self, action: #selector(responderPressed(sender:)), for: .primaryActionTriggered)
        return button
    }()

    let planActionButton: UIButton = {
        let button = makeButton(withText: "button.addTarget(nil)")
        button.addTarget(nil, action: .performFetchWeatherAction, for: .primaryActionTriggered)
        return button
    }()

    func setupViews() {
        navigationItem.title = "Responder Chain"

        let stackView = makeVerticalStackView()
        stackView.addArrangedSubview(sharedActionButton)
        stackView.addArrangedSubview(planActionButton)

        view.addSubview(stackView)

        stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        stackView.topAnchor.constraint(equalToSystemSpacingBelow: view.safeAreaLayoutGuide.topAnchor, multiplier: 3).isActive = true
    }

    @objc func responderPressed(sender: Any?) {
        UIApplication.shared.sendAction(
            #selector(ResponderAction.fetchWeather), to: nil, // target = nil
            from: self, for: nil)
    }
}

@objc protocol ResponderAction: AnyObject {
    func fetchWeather(sender: Any?)
}

private extension Selector {
    static let performFetchWeatherAction = #selector(ResponderAction.fetchWeather(sender:))
}

Passing Data

Because responder chain only supports three method signatures

- (void)action
- (void)action:(id)sender
- (void)action:(id)sender forEvent:(UIEvent *)event

We can't pass data like we would in a normal method. It has to be in one of the above forms.

func didSelectRowAt(_ indexPath: IndexPath) // 💥 «Unrecognized Selector Sent to Instance» Error In Xcode.

What that means is that to pass data, you have to include it as a property in the sender sent via the action.

@objc protocol ListAction: AnyObject {
  func didSelectItem(_ sender: ListViewController)
}

override func tableView(_: UITableView, didSelectRowAt _: IndexPath) {
  UIApplication.shared.sendAction(
     #selector(ListAction.didSelectItem(_:)), 
     to: nil, from: self, for: nil)
}

extension RootViewController: ListAction {
  func didSelectItem(_ sender: ListViewController) {
    if let object = sender.selectedObject { // 🚀 Passed data
      // present the detail view controller
    }
}

Links that help