Reactivecocoa: Привязки управления для `Action`s.

Созданный на 4 окт. 2016  ·  22Комментарии  ·  Источник: ReactiveCocoa/ReactiveCocoa

Есть два 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)

но с разделенным владением через нашу семантику привязки от слабого к слабому.

enhancement

Самый полезный комментарий

Тогда я это спрячу. Будь проще.

Все 22 Комментарий

Не забывайте, что нам также понадобится это для эквивалентной функциональности:

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 :

  1. Изменяемые ключи, не требующие наблюдения за средствами или значениями.
    например, UIControl.isEnabled , UILabel.text , UIView.isHidden .

Модель как BindingTarget s.

  1. Управляйте событиями и уведомлениями о вызовах методов.
    например, UIControl.trigger(for: .valueChanged) , UITableViewCell.trigger(for: #selector(prepareForReuse))

Модель как Signal s.

  1. Изменяемые ключи + соответствующее управляющее событие, которое публикуется при взаимодействии с пользователем.
    например, UITextField.text , NSTextField.text , UISwitch.isOn

# 3229

  1. [ _Связь Action с элементом управления.
    (Это просто удобный шаблон, построенный на основе [1] и [2].)
    например, 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. Я не уверен, что мы этого хотим. Как следует определять разрешающее состояние? И состояния включения всех действий?

И мне это не кажется правильным.

Вот наши варианты:

  1. Откажитесь от включения полностью
  2. И состояние включения всех действий
  3. ИЛИ все действия включены
  4. Включить, если есть ровно 1 действие
  5. Включить в .pressed и т. Д., А не в setAction(:for:) напрямую

(5) Мне кажется, лучшее решение. Обычный случай - это просто и удобно. Если вы хотите сделать что-то более сложное, вы все равно можете это сделать.

(5) означает, что setAction будет отличаться от выполнения нескольких actionN <~ reactive.trigger(for: .specificEvent) . Более того, что должно произойти, если элемент управления содержит несколько слотов для удобных команд?

По-прежнему все по-другому:

  1. Отмена бывает разной
  2. Допускается только одно действие на событие
  3. Он передает управление как вход в 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:) ?

Тогда я это спрячу. Будь проще.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

BrettThePark picture BrettThePark  ·  4Комментарии

danishin picture danishin  ·  4Комментарии

porridgec picture porridgec  ·  3Комментарии

gabro picture gabro  ·  5Комментарии

tunidev picture tunidev  ·  3Комментарии