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.
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.
Implementado em https://github.com/ReactiveCocoa/ReactiveCocoa/pull/3210/commits/f983a746f915a9a517e9e076c19581724075c562. Vamos ver como os outros pensam. 😆
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
:
UIControl.isEnabled
, UILabel.text
, UIView.isHidden
.Modelo como BindingTarget
s.
UIControl.trigger(for: .valueChanged)
, UITableViewCell.trigger(for: #selector(prepareForReuse))
Modelo como Signal
s.
UITextField.text
, NSTextField.text
, UISwitch.isOn
# 3229
Action
a um controle. 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:
.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:
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.
Comentários muito úteis
Vou esconder então. Mantenha simples.