Reactivecocoa: Steuerbindungen für `Action`s.

Erstellt am 4. Okt. 2016  ·  22Kommentare  ·  Quelle: ReactiveCocoa/ReactiveCocoa

Es gibt zwei APIs, die das Befehlsmuster verwenden. Wie @sharplet sagte, könnten diese von ReactiveObjC übernommen worden sein, zB button.rac_command .

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

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

Es fühlt sich einfach falsch an, wenn die Ansicht die Action mit einer starken Referenz besitzt. Das Eigentum an Action (indirekt über CocoaAction ) sollte der ursprünglichen Ebene (sei es VC oder VM) bleiben.

Daher wird nach einer langen Diskussion mit @liscio und @sharplet vorgeschlagen, eine neue Schnittstelle speziell für Action die mit UIControl und NSControl s verbunden wird. Das Konzept lautet wie folgt:

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:)`.
}

Mit dieser API würden wir alle Action Bindungsziele aus den UIControl- und NSControl-Unterklassen entfernen, da die API alle hätte abdecken sollen.

Im weiteren Sinne würden wir das Ziel-Aktions-Muster hinter @IBAction und @IBOutlet imitieren.

Im Interface Builder können wir eine _Sent Action_ eines UIButton an eine _Received Action_ (eine @IBAction Methode) eines UIViewControllers binden, und der UIButton sendet eine Nachricht an das schwach referenzierte Ziel.

In unserem Fall würden wir eine Sammlung von Ereignissignalen auf den Kontrollen aussetzen. Mit Action s als verbindliche Ziele kann Action eine ähnliche Rolle wie @IBAction . Beispielsweise:

// 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)

die Äquivalente von sind

confirmButton.rac_pressed.value = CocoaAction(commitAction)

aber mit einem entkoppelten Besitz über unsere schwache zu schwache Bindungssemantik.

enhancement

Hilfreichster Kommentar

Werde es dann verstecken. Halte es einfach.

Alle 22 Kommentare

Vergessen Sie nicht, dass wir dies auch für eine gleichwertige Funktionalität benötigen:

confirmButton.rac.isEnabled <~ commitAction.isEnabled

Hm, das habe ich übersehen. Es scheint, dass wir dann mit Action All-In gehen sollten.

@andersio Ich bin mir nicht sicher, was Sie damit meinen und warum es dazu geführt hat, dass dieses Problem geschlossen wurde.

Um das anzugehen, was @sharplet hervorruft , denke ich, dass es in Ordnung ist, die Weitergabe von Werten an den isEnabled Einlass auf einer Schaltfläche zu trennen.

Diese Tatsache in einer praktischen Methode / einem Muster zu verbergen, bringt uns meiner Meinung nach nicht viel.

Es fühlt sich einfach falsch an, die Ansicht zu haben, die die Aktion mit einem starken Bezug besitzt. Das Eigentum an der Aktion (indirekt über CocoaAction) sollte ihrer ursprünglichen Schicht (sei es VC oder VM) überlassen werden.

Ich glaube nicht, dass ich zustimme. Warum fühlt sich das für dich falsch an?

Die schwache Referenzierung ist ein winziger Teil der vorgeschlagenen API, die einen einzelnen Satz von UIControl Methoden pusht, der alle Steuerelemente abdeckt, die das Befehlsmuster verwenden (über Action ).

In Rex war es exklusiv für UIButton mit einer spezifischen Implementierung dafür.

AFAIK, ein Action repräsentiert normalerweise einen Befehl, gehört zu einem Ansichtsmodell und weist wahrscheinlich sowohl interne als auch externe Abhängigkeiten von seinem Status auf. Wie beim Kakao-Zielaktionsmuster sehe ich nicht, warum es eine starke Referenz sein sollte, wenn es einen offensichtlichen Besitzer hat.

Die Rex-API, die eine starke Referenz verwendet, ist wahrscheinlich ein Artefakt des Verhaltens von CocoaAction , das beibehalten werden muss, damit Werte an das umschlossene Action .

Die hier vorgeschlagene API nimmt stattdessen direkt Action .

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

Vorgeschlagen (funktioniert bei allen 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:)`.
}

Im Allgemeinen haben wir drei Klassen von UI-Zuständen + eine speziell für das Befehlsmuster mit Action :

  1. Veränderliche Schlüssel, die keine zu beachtenden Mittel oder Werte haben.
    zB UIControl.isEnabled , UILabel.text , UIView.isHidden .

Modellieren Sie als BindingTarget s.

  1. Steuern Sie Ereignisse und Benachrichtigungen über Methodenaufrufe.
    zB UIControl.trigger(for: .valueChanged) , UITableViewCell.trigger(for: #selector(prepareForReuse))

Modellieren Sie als Signal s.

  1. Mutable Keys + entsprechendes Kontrollereignis, das bei Benutzerinteraktionen gepostet wird.
    zB UITextField.text , NSTextField.text , UISwitch.isOn

#3229

  1. [ _Verknüpfen von Action mit einem Steuerelement.
    (Dies ist nur ein praktisches Muster, das auf [1] und [2] aufbaut.)
    zB UIButton.rac_command

_Dieses Problem. _

@andersio Ich denke, dass removeAction() leider auch angeben muss, für welche Kontrollereignisse Sie die Aktion entfernen, _oder_ nur für 1.0 würde ich es in removeAllActions() umbenennen und eine Notiz zu setAction() hinzufügen

Es ist umstritten. IMO mehrere Aktionen sind möglicherweise nicht sinnvoll, da in diesem Fall mehrere Aktionen gleichzeitig isEnabled manipulieren können (= irgendwie indeterministisch).

Warum nicht (Swift-)Eigenschaften hinzufügen, die die gegebene Aktion direkt festlegen?

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

Oder war das vielleicht in Ihrem Abschnitt some convenience overloads ?

Ich denke, es ist in Ordnung, wenn wir eine generische API hinzufügen möchten, aber ich denke, die Komfort-APIs sind wichtiger. Das ist die API, die ich der Welt vorführen würde. Ich finde so etwas sehr überzeugend:

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

Solange wir mit setAction(…) , sollte das Entfernen wahrscheinlich durch Festlegen einer Aktion nil . Keine separate API erforderlich.

Das sieht zweifellos besser aus, ja. Diese sollten unbedingt aufbewahrt werden. Ich würde trotzdem vorschlagen, CocoaAction einen schwachen Bezug zu Action .

setAction wird aktualisiert ( 790b3e8 ) und pressed ist zurück ( 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
    )
}

Ich denke, dass die API mehrere Aktionen unterstützen muss. Das sollte also gelten:

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

Warum referenziert CocoaAction schwach auf Action ?

@mdiep

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

Das würde bedeuten, dass der Aktivierungszustand von button sowohl an action1.isEnabled als auch an action2.isEnabled gebunden ist. Ich bin mir nicht sicher, ob wir das wollen. Wie soll der Aktivierungszustand bestimmt werden? Ein AND aller Action s' Aktivierungsstatus?

Warum verweist CocoaAction schwach auf Action?

Habe es zurückgesetzt. Ich würde zugeben, dass es im Moment (und während der Geschichte von Rex) keine Probleme verursacht.

Das würde bedeuten, dass der Aktivierungszustand der Schaltfläche sowohl an action1.isEnabled als auch an action2.isEnabled gebunden ist. Ich bin mir nicht sicher, ob wir das wollen. Wie soll der Aktivierungszustand bestimmt werden? Ein UND des Aktivierungszustands aller Aktionen?

UND scheint mir nicht richtig.

Hier sind unsere Optionen:

  1. Gib das Aktivieren komplett auf
  2. UND der aktivierte Status aller Aktionen
  3. ODER aktivierter Status aller Aktionen
  4. Aktivieren, wenn es genau 1 Aktion gibt
  5. In .pressed usw. aktivieren und nicht direkt in setAction(:for:)

(5) Scheint mir die beste Lösung zu sein. Der gewöhnliche Fall ist einfach und bequem. Wenn Sie etwas Komplexeres tun möchten, können Sie dies immer noch tun.

(5) bedeutet, dass setAction keinen Unterschied zu mehreren actionN <~ reactive.trigger(for: .specificEvent) . Was sollte außerdem passieren, wenn ein Steuerelement mehrere Komfortbefehlsslots enthält?

Darin ist es noch anders:

  1. Stornierung ist anders
  2. Es erlaubt nur eine Aktion pro Ereignis
  3. Es übergibt das Steuerelement als Eingabe an das CocoaAction

Ansonsten sind sie ungefähr gleich, ja. Aber ich denke, das ist in Ordnung.

Wenn mehr als eine Aktion unterstützt werden soll, sollten wir sie wahrscheinlich in addAction umbenennen.

Sagen wir , wenn wir einen hinzufügen propagateEnablingState Argument setAction , müssen wir noch entscheiden , wie der Vektor zu behandeln isEnabled . (Konfigurierbar? zB und/oder/undefiniert?)

Ich bin mir nicht sicher, ob es sich lohnt, >1 Aktion in der hier diskutierten "Bequemlichkeitseigenschaft" zu unterstützen, da wir die alternative Verwendung (Aktionen einzeln auslösen mit trigger(for:) ) für Leute belassen haben, die mehr als eine benötigen.

Ich sehe keinen guten Grund, einer Schaltfläche auf diese Weise mehr als eine Aktion hinzuzufügen, angesichts der einfach zu verwendenden Alternative (die zufällig auch mit der Verwendung unserer anderen Signal/BindingTarget-Sachen übereinstimmt).

Wenn Sie eine "einfache API" wünschen, verwenden Sie im Grunde die Eigenschaft action wie hier. Sein aktivierter Zustand wird in der Schaltfläche widergespiegelt.

Wenn Sie eine Schaltfläche an >1 Aktion anhängen möchten, verwenden Sie deren individuelle trigger s und schließen Sie dann ein eigenes map an, um sich in die isEnabled BindingTarget . Klingt das vernünftig?

Grundsätzlich, wenn Sie eine "einfache API" wünschen, verwenden Sie die Aktionseigenschaft wie hier. Sein aktivierter Zustand wird in der Schaltfläche widergespiegelt.

Wenn Sie eine Schaltfläche an die Aktion >1 anhängen möchten, verwenden Sie deren individuelle Trigger und schließen Sie dann eine eigene Map an, um sie in das isEnabled BindingTarget einzubinden. Klingt das vernünftig?

Das klingt für mich vernünftig.

Brauchen wir dann überhaupt setAction(:for:) ?

Werde es dann verstecken. Halte es einfach.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen