Reactivecocoa: Vinculações de controle para `Action`s.

Criado em 4 out. 2016  ·  22Comentários  ·  Fonte: ReactiveCocoa/ReactiveCocoa

Existem duas APIs que usam o padrão de comando. Como @sharplet disse, eles podem ter sido trazidos do ReactiveObjC, por exemplo, button.rac_command .

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

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

Parece errado ter a visão possuindo Action com uma referência forte. A propriedade de Action (indiretamente por meio de CocoaAction ) deve ser deixada para sua camada de origem (seja ela VC ou VM).

Portanto, após uma longa discussão com @liscio e @sharplet , é proposto ter uma nova interface criada especificamente para Action conectando-se com UIControl e NSControl s. O conceito é assim:

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

Com esta API, removeríamos todos os destinos de ligação Action das subclasses UIControl e NSControl, uma vez que a API deveria ter coberto todos eles.

Em um sentido mais amplo, estaríamos imitando o padrão de ação-alvo por trás de @IBAction e @IBOutlet .

No Interface Builder, podemos vincular uma _Sent Action_ de um UIButton a uma _Received Action_ (um método @IBAction ) de um UIViewController, e o UIButton envia uma mensagem ao alvo fracamente referenciado.

Em nosso caso, exporíamos uma coleção de sinais de evento nos controles. Com Action s como alvos de ligação, Action pode ter uma função semelhante a @IBAction . Por exemplo:

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

que são equivalentes de

confirmButton.rac_pressed.value = CocoaAction(commitAction)

mas com uma propriedade desacoplada por meio de nossa semântica de ligação fraca a fraca.

enhancement

Comentários muito úteis

Vou esconder então. Mantenha simples.

Todos 22 comentários

Não se esqueça de que também precisamos disso para uma funcionalidade equivalente:

confirmButton.rac.isEnabled <~ commitAction.isEnabled

Hmm, eu esqueci isso. Parece que devemos ir all-in com Action então.

@andersio Não tenho certeza do que você quer dizer com isso e por que o problema foi resolvido.

Para abordar o que @sharplet traz, acho que é bom separar a propagação de valores para a entrada isEnabled em um botão.

Esconder esse fato em um método / padrão de conveniência não está nos comprando muito, na minha opinião.

Parece errado ter a visão de propriedade da Ação com uma referência forte. A propriedade da ação (indiretamente via CocoaAction) deve ser deixada para sua camada de origem (seja ela VC ou VM).

Eu não acho que concordo. Por que isso parece errado para você?

A referência fraca é um pequeno pedaço da API proposta, que empurra um único conjunto de métodos UIControl que cobre todos os controles usando o padrão de comando (via Action ).

No Rex, era exclusivo de UIButton com uma implementação específica para ele.

AFAIK, um Action geralmente representa um comando, é propriedade de um modelo de visão e provavelmente tem dependências internas e externas de / em seu estado. Como o padrão de ação-alvo Cocoa, não vejo por que deveria ser uma referência forte quando tem um proprietário aparente.

A API Rex usando uma referência forte é provavelmente um artefato de como CocoaAction se comporta, que deve ser retido para que os valores possam ser propagados para Action embrulhados.

A API proposta aqui, em vez disso, leva Action diretamente.

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

Proposto (funciona em todos os 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 modo geral, temos três classes de estados de IU + uma especificamente para o padrão de comando com Action :

  1. Chaves mutáveis ​​que não possuem meios ou valores a serem observados.
    por exemplo, UIControl.isEnabled , UILabel.text , UIView.isHidden .

Modelo como BindingTarget s.

  1. Controle de eventos e notificações de chamadas de método.
    por exemplo, UIControl.trigger(for: .valueChanged) , UITableViewCell.trigger(for: #selector(prepareForReuse))

Modelo como Signal s.

  1. Chaves mutáveis ​​+ evento de controle correspondente que posta nas interações do usuário.
    por exemplo, UITextField.text , NSTextField.text , UISwitch.isOn

# 3229

  1. [ _Associar Action a um controle.
    (Este é apenas um padrão de conveniência construído em cima de [1] e [2].)
    por exemplo, UIButton.rac_command

_Esse assunto. _

@andersio Acho que infelizmente removeAction() também terá que especificar os eventos de controle para os quais você está removendo a ação, _ou_ apenas para 1.0, eu renomearia removeAllActions() e adicionaria uma nota a setAction() para indicar que o controle atualmente só lida com uma única ação sendo enganchada usando esta "API fácil", já que é assim que 99,9% das pessoas a usarão, de qualquer maneira.

É discutível. Ações múltiplas IMO podem não fazer sentido, já que neste caso ações múltiplas podem manipular isEnabled simultaneamente (= meio indeterminista).

Por que não adicionar propriedades (Swift) que definem diretamente a ação dada?

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

Ou talvez isso tenha sido incluído em sua seção some convenience overloads ?

Acho que está tudo bem se quisermos adicionar uma API genérica, mas acho que as APIs de conveniência são mais importantes. Essa é a API que eu mostraria para o mundo. Acho que algo assim é muito atraente:

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

Enquanto estivermos usando setAction(…) , a remoção provavelmente deve ser feita definindo uma ação nil . Não há necessidade de uma API separada.

Isso, sem dúvida, parece melhor, sim. Definitivamente, estes devem ser mantidos. Eu ainda proporia fazer CocoaAction ter uma referência fraca para Action embora.

setAction é atualizado ( 790b3e8 ), e pressed está de volta ( 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
    )
}

Acho que a API precisaria oferecer suporte a várias ações. Portanto, isso deve ser válido:

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

Por que CocoaAction referencia fracamente Action ?

@mdiep

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

Isso significaria que o estado de ativação de button está vinculado a action1.isEnabled e action2.isEnabled . Não tenho certeza se é isso que queremos. Como o estado de habilitação deve ser determinado? Um AND de todos os Action s 'estado de ativação?

Por que CocoaAction faz uma referência fraca ao Action?

Revertido. Eu admito que não causa problemas por enquanto (e durante a história do Rex).

Isso significaria que o estado de ativação do botão está vinculado a action1.isEnabled e action2.isEnabled. Não tenho certeza se é isso que queremos. Como o estado de habilitação deve ser determinado? Um E de estado de ativação de todas as ações?

E não parece certo para mim.

Aqui estão nossas opções:

  1. Desista de habilitar completamente
  2. E estado habilitado de todas as ações
  3. OU estado habilitado de todas as ações
  4. Habilitar iff houver exatamente 1 ação
  5. Habilite em .pressed , etc. e não em setAction(:for:) diretamente

(5) Parece ser a melhor solução para mim. O caso comum é fácil e conveniente. Se você quiser fazer algo mais complexo, ainda será capaz.

(5) significa que setAction faria diferença em fazer actionN <~ reactive.trigger(for: .specificEvent) múltiplos. Além disso, o que deve acontecer se um controle contiver vários slots de comando de conveniência?

Ainda é diferente nisso:

  1. O cancelamento é diferente
  2. Permite apenas uma ação por evento
  3. Ele passa o controle como entrada para CocoaAction

Caso contrário, eles são praticamente os mesmos, sim. Mas acho que está tudo bem.

Se o plano é oferecer suporte a mais de uma ação, provavelmente devemos renomeá-lo para addAction .

Digamos que se adicionarmos um argumento propagateEnablingState a setAction , ainda teremos que decidir como lidar com o vetor de isEnabled . (Configurável? Por exemplo, e / ou / indefinido?)

Não tenho certeza se vale a pena suportar> 1 ação na "propriedade de conveniência" que está sendo discutida aqui, pois deixamos o uso alternativo (ações de acionamento individual com trigger(for:) ) para pessoas que precisam de mais de um.

Não vejo um bom motivo para adicionar mais de uma ação a um botão dessa maneira, dada a alternativa fácil de usar (que também coincide com o uso de nossas outras coisas Signal / BindingTarget.)

Basicamente, se você quiser uma "API simples", use a propriedade action como aqui. Seu estado ativado é refletido no botão.

Se você quiser anexar um botão a> 1 ação, use seus trigger s individuais e, em seguida, conecte um map seu para conectar o isEnabled BindingTarget . Isso parece razoável?

Basicamente, se você quiser uma "API simples", use a propriedade action como aqui. Seu estado ativado é refletido no botão.

Se você deseja anexar um botão a> 1 ação, use seus gatilhos individuais e, em seguida, conecte um mapa seu para conectar-se ao isEnabled BindingTarget. Isso parece razoável?

Isso parece razoável para mim.

Então, precisamos de setAction(:for:) ?

Vou esconder então. Mantenha simples.

Esta página foi útil?
0 / 5 - 0 avaliações