Есть два API, которые используют шаблон команды. Как сказал @sharplet , они могли быть перенесены из ReactiveObjC, например button.rac_command
.
extension Reactive where Host: UIButton {
public var pressed: MutableProperty<CocoaAction> { ... }
}
extension Reactive where Host: UIBarButtonItem {
public var pressed: MutableProperty<CocoaAction> { ... }
}
Просто кажется неправильным иметь представление, владеющее Action
с сильной ссылкой. Право собственности на Action
(косвенно через CocoaAction
) должно быть оставлено его исходному уровню (будь то VC или VM).
Поэтому после продолжительного обсуждения с @liscio и @sharplet предлагается создать новый интерфейс, специально созданный для Action
соединяющегося с UIControl
и NSControl
s. Концепция такая:
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:)`.
}
С помощью этого API мы удалим все целевые объекты привязки Action
из подклассов UIControl и NSControl, поскольку API должен охватывать их все.
В более широком смысле мы бы имитировали шаблон целевого действия, стоящий за @IBAction
и @IBOutlet
.
В Интерфейсном Разработчике мы можем привязать _Sent Action_ UIButton к _Received Action_ ( @IBAction
метод) UIViewController, и UIButton отправляет сообщение в слабо упоминаемую цель.
В нашем случае мы бы выставили набор сигналов событий для элементов управления. С Action
s в качестве целей привязки Action
может иметь роль, аналогичную @IBAction
. Например:
// 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)
которые являются эквивалентами
confirmButton.rac_pressed.value = CocoaAction(commitAction)
но с разделенным владением через нашу семантику привязки от слабого к слабому.
Не забывайте, что нам также понадобится это для эквивалентной функциональности:
confirmButton.rac.isEnabled <~ commitAction.isEnabled
Хм, я это не заметил. Похоже, тогда нам стоит пойти олл-ин с Action
.
@andersio Я не уверен, что вы имеете в виду и почему это привело к закрытию этой проблемы.
Чтобы обратиться к тому, что вызывает @sharplet , я думаю, что можно разделить распространение значений на вход isEnabled
на кнопке.
На мой взгляд, сокрытие этого факта в удобном методе / шаблоне не особо нам выгодно.
Реализовано в https://github.com/ReactiveCocoa/ReactiveCocoa/pull/3210/commit/f983a746f915a9a517e9e076c19581724075c562. Посмотрим, как думают другие. 😆
Просто кажется неправильным иметь представление, владеющее действием с сильной ссылкой. Право собственности на действие (косвенно через CocoaAction) следует оставить его исходному уровню (будь то VC или VM).
Не думаю, что согласен. Почему вам это кажется неправильным?
Слабые ссылки - это крошечные кусочки предлагаемого API, который подталкивает единственный набор методов UIControl
который охватывает все элементы управления с использованием шаблона команд (через Action
).
В Rex это было эксклюзивно для UIButton
с определенной реализацией для него.
AFAIK, Action
обычно представляет команду, принадлежит модели представления и, вероятно, имеет как внутренние, так и внешние зависимости от / от своего состояния. Как и шаблон целевого действия Какао, я не понимаю, почему он должен быть сильной ссылкой, если у него есть очевидный владелец.
Rex API, использующий сильную ссылку, вероятно, является артефактом того, как ведет себя CocoaAction
, который необходимо сохранить, чтобы значения можно было распространить на завернутый Action
.
Предлагаемый здесь API напрямую принимает Action
.
Рекс 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 }
}
Предлагается (работает на всех 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:)`.
}
Вообще говоря, у нас есть три класса состояний пользовательского интерфейса + один специально для шаблона команды с Action
:
UIControl.isEnabled
, UILabel.text
, UIView.isHidden
.Модель как BindingTarget
s.
UIControl.trigger(for: .valueChanged)
, UITableViewCell.trigger(for: #selector(prepareForReuse))
Модель как Signal
s.
UITextField.text
, NSTextField.text
, UISwitch.isOn
# 3229
Action
с элементом управления. UIButton.rac_command
_Эта проблема. _
@andersio Я думаю, что, к сожалению, в removeAction()
также нужно будет указать, для каких событий управления вы удаляете действие, _или_ только для 1.0 я бы переименовал его в removeAllActions()
и добавил примечание к setAction()
чтобы указать, что элемент управления в настоящее время обрабатывает только одно действие, подключенное с помощью этого «простого API», так как в любом случае 99,9% людей будут его использовать.
Это спорно. Множественные действия IMO могут не иметь смысла, поскольку в этом случае несколько действий могут одновременно манипулировать isEnabled
(= своего рода недетерминированный).
Почему бы не добавить свойства (Swift), которые напрямую задают данное действие?
extension Reactive where Base: UIButton {
var pressed: CocoaAction {
get { … }
set { … }
}
}
Или, может быть, это было включено в ваш раздел some convenience overloads
?
Я думаю, что это нормально, если мы хотим добавить общий API, но я думаю, что удобные API более важны. Это API, которым я бы хвастался всему миру. Я думаю, что что-то вроде этого очень убедительно:
button.reactive.pressed = CocoaAction(viewModel.action)
Поскольку мы используем setAction(…)
, удаление, вероятно, следует производить, задав действие nil
. Нет необходимости в отдельном API.
Это, несомненно, выглядит лучше, да. Их обязательно нужно сохранить. Я все же предлагаю сделать так, чтобы в CocoaAction
была слабая ссылка на Action
.
setAction
обновлен ( 790b3e8 ), а pressed
вернулся ( 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
)
}
Я думаю, что API должен поддерживать несколько действий. Итак, это должно быть действительным:
button.reactive.setAction(action1, for: .touchUpInside)
button.reactive.setAction(action2, for: .touchUpOutside)
// button now has 2 actions set for 2 different events
Почему CocoaAction
слабо ссылается на Action
?
@mdiep
// button now has 2 actions set for 2 different events
Это будет означать, что состояние включения button
привязано как к action1.isEnabled
и к action2.isEnabled
. Я не уверен, что мы этого хотим. Как следует определять разрешающее состояние? AND
из всех состояний включения Action
s?
Почему CocoaAction слабо ссылается на действие?
Отменил. Я должен признать, что это не доставляет проблем сейчас (и во время истории Рекса).
Это означало бы, что состояние включения кнопки привязано как к action1.isEnabled, так и к action2.isEnabled. Я не уверен, что мы этого хотим. Как следует определять разрешающее состояние? И состояния включения всех действий?
И мне это не кажется правильным.
Вот наши варианты:
.pressed
и т. Д., А не в setAction(:for:)
напрямую(5) Мне кажется, лучшее решение. Обычный случай - это просто и удобно. Если вы хотите сделать что-то более сложное, вы все равно можете это сделать.
(5) означает, что setAction
будет отличаться от выполнения нескольких actionN <~ reactive.trigger(for: .specificEvent)
. Более того, что должно произойти, если элемент управления содержит несколько слотов для удобных команд?
По-прежнему все по-другому:
CocoaAction
В остальном они примерно такие же, да. Но я думаю, что это нормально.
Если план состоит в поддержке более чем одного действия, нам, вероятно, следует переименовать его в addAction
.
Скажем, если мы добавим аргумент propagateEnablingState
к setAction
, нам все равно придется решить, как обрабатывать вектор isEnabled
. (Настраивается? Например, и / или / не определено?)
Я не уверен, что стоит поддерживать> 1 действие в обсуждаемом здесь «удобном свойстве», поскольку мы оставили альтернативное использование (индивидуальное инициирование действий с помощью trigger(for:)
) для людей, которым требуется более одного.
Я не вижу веских причин для добавления более одного действия к кнопке таким образом, учитывая простую в использовании альтернативу (которая также соответствует использованию других наших материалов Signal / BindingTarget.)
По сути, если вам нужен «простой API», вы используете свойство action
как здесь. Его включенное состояние отражается на кнопке.
Если вы хотите прикрепить кнопку к> 1 действию, вы используете их индивидуальные trigger
s, а затем подключаете собственный map
чтобы подключиться к isEnabled
BindingTarget
. Звучит разумно?
По сути, если вам нужен «простой API», вы используете свойство action, как здесь. Его включенное состояние отражается на кнопке.
Если вы хотите прикрепить кнопку к> 1 действию, вы используете их отдельные триггеры, а затем подключаете собственную карту для подключения к isEnabled BindingTarget. Звучит разумно?
Для меня это звучит разумно.
Так зачем нам тогда вообще setAction(:for:)
?
Тогда я это спрячу. Будь проще.
Самый полезный комментарий
Тогда я это спрячу. Будь проще.