Reactivecocoa: Contrôlez les liaisons pour les « Actions ».

Créé le 4 oct. 2016  ·  22Commentaires  ·  Source: ReactiveCocoa/ReactiveCocoa

Il existe deux API qui utilisent le modèle de commande. Comme @sharplet l'a dit, ceux-ci pourraient avoir été button.rac_command .

extension Reactive where Host: UIButton {
    public var pressed: MutableProperty<CocoaAction> { ... }
}

extension Reactive where Host: UIBarButtonItem {
    public var pressed: MutableProperty<CocoaAction> { ... }
}

Il est tout simplement faux d'avoir la vue possédant le Action avec une référence forte. La propriété du Action (indirectement via CocoaAction ) doit être laissée à sa couche d'origine (que ce soit VC ou VM).

Par conséquent, après une longue discussion avec @liscio et @sharplet , il est proposé d'avoir une nouvelle interface spécialement conçue pour Action connectant avec UIControl et NSControl s. Le concept est comme :

extension Reactive where Base: UIControl {
    /// Create a trigger signal for a specific set of control events.
    public func trigger(for controlEvents: UIControlEvents) -> Signal<(), NoError>

    /// The action is weakly referenced internally.
    public func setAction<Input, Output, Error>(_ action: Action<Input, Output, Error>, for controlEvents: UIControlEvents, inputTransform: (Self, UIControlEvents) -> Input)

    public func removeAction()

    /// ... some convenience overloads of `setAction(_:for:inputTransform:)`.
}

Avec cette API, nous supprimerions toutes les cibles de liaison Action des sous-classes UIControl et NSControl, car l'API aurait dû les couvrir toutes.

Dans un sens plus large, nous imiterions le modèle d'action cible derrière @IBAction et @IBOutlet .

Dans l'Interface Builder, nous pouvons lier une _Action envoyée_ d'un UIButton à une _Action reçue_ (une méthode @IBAction ) d'un UIViewController, et le UIButton envoie un message à la cible faiblement référencée.

Dans notre cas, nous exposerions une collection de signaux d' événement sur les commandes. Avec Action s comme cibles de liaison, Action peut avoir un rôle similaire à @IBAction . Par example:

// Received Action <~ Sent Action
commitAction <~ confirmButton.touchUpInside

// The reverse `isEnabled` binding is now explicit & optional.
confirmButton.isEnabled <~ commitAction.isEnabled

// OR

confirmButton.setAction(commitAction, for: .touchUpInside)

qui sont des équivalents de

confirmButton.rac_pressed.value = CocoaAction(commitAction)

mais avec une propriété découplée via notre sémantique de liaison faible à faible.

enhancement

Commentaire le plus utile

Je vais le cacher alors. Rester simple.

Tous les 22 commentaires

N'oubliez pas que nous aurions également besoin de ceci pour une fonctionnalité équivalente :

confirmButton.rac.isEnabled <~ commitAction.isEnabled

Hum, j'avais oublié ça. Il semble que nous devrions faire tapis avec Action alors.

@andersio, je ne sais pas ce que vous entendez par là et pourquoi cela a entraîné la fermeture de ce problème.

Pour répondre à ce que @sharplet apporte, je pense qu'il est bon de séparer la propagation des valeurs à l'entrée isEnabled sur un bouton.

Cacher ce fait dans une méthode/un modèle pratique ne nous rapporte pas grand-chose, à mon avis.

Il est tout simplement inacceptable d'avoir la vue propriétaire de l'action avec une référence forte. La propriété de l'action (indirectement via CocoaAction) doit être laissée à sa couche d'origine (qu'elle soit VC ou VM).

Je ne pense pas être d'accord. Pourquoi cela vous semble-t-il mal ?

Le référencement faible est un tout petit peu de l'API proposée, qui pousse un seul ensemble de méthodes UIControl qui couvre tous les contrôles utilisant le modèle de commande (via Action ).

Dans Rex, c'était exclusif à UIButton avec une implémentation spécifique pour cela.

AFAIK, un Action représente généralement une commande, appartient à un modèle de vue et a probablement des dépendances internes et externes de/sur son état. Comme le modèle d'action cible de Cocoa, je ne vois pas pourquoi il devrait être une référence forte alors qu'il a un propriétaire apparent.

L'API Rex utilisant une référence forte est probablement un artefact du comportement de CocoaAction , qui doit être conservé afin que les valeurs puissent être propagées au Action .

L'API proposée ici prend plutôt un Action directement.

Rex 0.12 :

extension UIButton {
    public var rex_pressed: MutableProperty<CocoaAction> { get }

    // This one would become `BindingTarget`. Irrelevant to this issue.
    public var rex_title: MutableProperty<String> { get }
}

Proposé (Fonctionne sur tous les UIControl s):

extension Reactive where Base: UIControl {
    /// The action is weakly referenced internally.
    public func setAction<Input, Output, Error>(
        _ action: Action<Input, Output, Error>,
        for controlEvents: UIControlEvents,
        inputTransform: (Self, UIControlEvents) -> Input
    )

    public func removeAction()

    /// ... some convenience overloads of `setAction(_:for:inputTransform:)`.
}

De manière générale, nous avons trois classes d'états d'UI + une spécifiquement pour le modèle de commande avec Action :

  1. Clés mutables qui n'ont aucun moyen ou valeur à observer.
    par exemple UIControl.isEnabled , UILabel.text , UIView.isHidden .

Modéliser comme BindingTarget s.

  1. Événements de contrôle et notifications d'appels de méthode.
    par exemple UIControl.trigger(for: .valueChanged) , UITableViewCell.trigger(for: #selector(prepareForReuse))

Modéliser comme Signal s.

  1. Touches mutables + événement de contrôle correspondant qui s'affiche lors des interactions de l'utilisateur.
    par exemple UITextField.text , NSTextField.text , UISwitch.isOn

#3229

  1. [ _Associer Action à un champ.
    (Ceci est juste un modèle de commodité construit au-dessus de [1] et [2].)
    par exemple UIButton.rac_command

_Ce problème. _

@andersio Je pense que malheureusement removeAction() devra également spécifier pour quels événements de contrôle vous supprimez l'action, _ou_ juste pour 1.0 je le renommerais removeAllActions() et ajouterais une note à setAction() pour indiquer que le contrôle ne gère actuellement qu'une seule action accrochée à l'aide de cette "API facile" puisque c'est de toute façon ainsi que 99,9% des personnes l'utiliseront.

C'est discutable. Les actions multiples de l'OMI peuvent ne pas avoir de sens, car dans ce cas, plusieurs actions peuvent manipuler isEnabled simultanément (= un peu indéterministe).

Pourquoi ne pas ajouter des propriétés (Swift) qui définissent directement l'action donnée ?

extension Reactive where Base: UIButton {
    var pressed: CocoaAction {
        get { … }
        set { … }
    } 
}

Ou peut-être était-ce inclus dans votre section some convenience overloads ?

Je pense que c'est bien si nous voulons ajouter une API générique, mais je pense que les API de commodité sont plus importantes. C'est l'API que je montrerais au monde. Je pense que quelque chose comme ça est très convaincant :

button.reactive.pressed = CocoaAction(viewModel.action)

Tant que nous allons avec setAction(…) , la suppression devrait probablement être effectuée en définissant une action nil . Pas besoin d'une API séparée.

C'est sans aucun doute mieux, ouais. Ceux-ci doivent absolument être conservés. Je proposerais toujours de faire en sorte que CocoaAction ait une faible référence au Action .

setAction est mis à jour ( 790b3e8 ), et pressed est de retour ( 790b3e8 ).

extension Reactive where Base: UIButton {
    public var pressed: CocoaAction<Base> { get nonmutating set }
}

extension Reactive where Base: UIControl {
    public var action: CocoaAction<Base>? { get }
    public var controlEventsForAction: UIControlEvents? { get }

    /// `CocoaAction` is retained by `self`, but `CocoaAction` weakly references `Action`.
    public func setAction(
        _ action: CocoaAction<Base>?,
        for controlEvents: UIControlEvents
    )
}

Je pense que l'API devrait prendre en charge plusieurs actions. Donc ça doit être valable :

button.reactive.setAction(action1, for: .touchUpInside)
button.reactive.setAction(action2, for: .touchUpOutside)
// button now has 2 actions set for 2 different events

Pourquoi CocoaAction référence faiblement Action ?

@mdiep

// button now has 2 actions set for 2 different events

Cela signifierait que l'état d'activation de button est lié à la fois à action1.isEnabled et à action2.isEnabled . Je ne sais pas si c'est ce que nous voulons. Comment déterminer l'état d'habilitation ? Un AND de tous les Action s' état d'activation ?

Pourquoi CocoaAction référence faiblement Action ?

Je l'ai retourné. J'admets que cela ne pose pas de problème pour l'instant (et pendant l'histoire de Rex).

Cela signifierait que l'état d'activation du bouton est lié à la fois à action1.isEnabled et action2.isEnabled. Je ne sais pas si c'est ce que nous voulons. Comment déterminer l'état d'habilitation ? Un ET de l'état d'activation de toutes les actions ?

ET ne me semble pas correct.

Voici nos options :

  1. Abandonner complètement l'activation
  2. ET état activé de toutes les actions
  3. OU état activé de toutes les actions
  4. Activer ssi il y a exactement 1 action
  5. Activer dans .pressed , etc. et pas directement dans setAction(:for:)

(5) Cela me semble être la meilleure solution. Le cas commun est facile et pratique. Si vous voulez faire quelque chose de plus complexe, vous pouvez toujours le faire.

(5) signifie que setAction n'aurait aucune différence par rapport à plusieurs actionN <~ reactive.trigger(for: .specificEvent) . De plus, que se passe-t-il si un contrôle contient plusieurs emplacements de commande de commodité ?

C'est quand même différent en cela :

  1. L'annulation est différente
  2. Il ne permet qu'une action par événement
  3. Il passe le contrôle comme entrée au CocoaAction

Sinon, c'est à peu près les mêmes, oui. Mais je pense que ça va.

Si le plan est de prendre en charge plus d'une action, nous devrions probablement le renommer en addAction .

Disons que si nous ajoutons un argument propagateEnablingState à setAction , nous devons encore décider comment gérer le vecteur de isEnabled . (Configurable ? Par exemple et/ou/non défini ?)

Je ne suis pas sûr que cela vaille la peine de prendre en charge >1 action dans la "propriété de commodité" dont il est question ici, car nous avons laissé l'utilisation alternative (déclencher individuellement des actions avec trigger(for:) ) pour les personnes qui en ont besoin de plusieurs.

Je ne vois pas de bonne raison d'ajouter plus d'une action à un bouton de cette manière étant donné l'alternative facile à utiliser (cela correspond également à l'utilisation de nos autres éléments Signal/BindingTarget.)

Fondamentalement, si vous voulez une "API simple", vous utilisez la propriété action comme ici. Son état activé est reflété dans le bouton.

Si vous souhaitez attacher un bouton à >1 action, utilisez leurs trigger s individuels, puis connectez votre propre map pour vous connecter au isEnabled BindingTarget . Cela semble-t-il raisonnable ?

Fondamentalement, si vous voulez une "API simple", vous utilisez la propriété action comme ici. Son état activé est reflété dans le bouton.

Si vous souhaitez attacher un bouton à >1 action, vous utilisez leurs déclencheurs individuels, puis vous connectez votre propre carte à connecter à isEnabled BindingTarget. Cela semble-t-il raisonnable ?

Cela me semble raisonnable.

Alors, avons-nous besoin de setAction(:for:) ?

Je vais le cacher alors. Rester simple.

Cette page vous a été utile?
0 / 5 - 0 notes