Пропустить событие из источника наблюдаемой, если новое событие из данной наблюдаемой было получено в данный интервал времени - PullRequest
0 голосов
/ 10 января 2019

Я пытаюсь написать метод для расширения UIView, который будет наблюдать долгое нажатие на данном представлении. Я знаю, что это можно сделать с помощью UILongPressGestureRecognizer, но я действительно хочу выяснить вопрос и сделать это таким образом.

Я пытался использовать оператор takeUntil, но он завершает наблюдаемую, но мне нужно пропустить значение и получить дальнейшие события.

Вопрос также может быть преобразован в: Как пропустить completed событие и продолжать получать дальнейшие события?

func observeLongPress(with minimumPressDuration: Double = 1) -> 
  Observable<Void> {

    let touchesBeganEvent = rx.methodInvoked(#selector(touchesBegan))
    let touchesEndedEvents = [#selector(touchesEnded), #selector(touchesCancelled)]
        .map(rx.methodInvoked)
    let touchesEndedEvent = Observable.merge(touchesEndedEvents)
    return touchesBeganEvent
        .observeOn(MainScheduler.instance)
        .delay(minimumPressDuration, scheduler: MainScheduler.instance)
        .takeUntil(touchesEndedEvent)
        .map { _ in }
}

Это будет работать, но завершит всю последовательность (как и предполагалось).

Ответ, если парится (как это всегда бывает), но через несколько часов я решил спросить. :)

Обновление

Плавающий ответ только что вылетел внутрь (~ 15 минут для этого), но я все еще заинтересован в ответе, потому что, может быть, я чего-то здесь упускаю.


    func observeLongPress(with minimumPressDuration: Double = 1) -> Observable<Void> {
          let touchesBeganEvent = rx.methodInvoked(#selector(touchesBegan))
          let touchesEndedEvents = [#selector(touchesEnded), #selector(touchesCancelled)]
            .map(rx.methodInvoked)
          let touchesEndedEvent = Observable.merge(touchesEndedEvents)

          return touchesBeganEvent
            .observeOn(MainScheduler.instance)
            .flatMapLatest { _ -> Observable<Void> in
              return Observable.just(())
                .delay(minimumPressDuration, scheduler: MainScheduler.instance)
                .takeUntil(touchesEndedEvent)
                .void()
          }
    }

1 Ответ

0 голосов
/ 11 января 2019

Ваш обновленный код не будет работать. Даже если вы не отправите завершенное событие из функции, оно все равно будет отправлено из takeUntil, и поэтому этот оператор не будет выдавать больше значений.

Тем не менее, эта идея может быть реализована. Поскольку ты сказал, что хочешь учиться, я буду писать весь мой мыслительный процесс, пока буду писать.

Сначала давайте наметим наши входы и выходы. Для входных данных у нас есть две Observables, и длительность, и всякий раз, когда мы имеем дело с длительностью, нам нужен планировщик. Для выходов у нас есть только одна наблюдаемая.

Итак, объявление функции выглядит так:

func filterDuration(first: Observable<Void>, second: Observable<Void>, duration: TimeInterval, scheduler: SchedulerType) -> Observable<Void> {
    // code goes here.
}

Мы будем сравнивать время срабатывания двух наблюдаемых, поэтому мы должны отследить это:

let firstTime = first.map { scheduler.now }
let secondTime = second.map { scheduler.now }

И поскольку мы сравниваем их, мы должны как-то их объединить. Мы могли бы использовать merge, combineLatest или zip ...

  • combineLatest сработает всякий раз, когда сработает какая-либо наблюдаемая, и даст нам последние значения из обеих наблюдаемых.
  • zip будет сопряжено, 1 на 1, события из обеих наблюдаемых. Это звучит интригующе, но может сломаться, если один из наблюдаемых срабатывает чаще, чем другой, поэтому он кажется немного ломким.
  • merge сработает при срабатывании любого из них, поэтому нам нужно отследить, какой из них выстрелил каким-то образом (возможно, с помощью перечисления).

Давайте использовать combineLatest:

Observable.combineLatest(firstTime, secondTime)

Это даст нам Observable<(RxTime, RxTime)>, так что теперь мы можем сопоставить наши два раза и сравнить их. Цель здесь - вернуть Bool, которое истинно, если второй раз больше первого раза более чем на duration.

    .map { arg -> Bool in
        let (first, second) = arg
        let tickDuration = second.timeIntervalSince(first)
        return duration <= tickDuration
    }

Теперь вышеприведенное будет срабатывать каждый раз, когда срабатывает любой из двух входов, но мы заботимся только о событиях, которые излучают true. Это требует filter.

.filter { $0 } // since the wrapped value is a Bool, this will accomplish our goal.

Теперь наша цепь испускает Bools, что всегда будет правдой, но мы хотим пустоту. Как насчет того, чтобы просто выбросить значение?

.map { _ in }

Собрав все вместе, мы получим:

func filterDuration(first: Observable<Void>, second: Observable<Void>, duration: TimeInterval, scheduler: SchedulerType) -> Observable<Void> {

    let firstTime = first.map { scheduler.now }
    let secondTime = second.map { scheduler.now }

    return Observable.combineLatest(firstTime, secondTime)
        .map { arg -> Bool in
            let (first, second) = arg
            let tickDuration = second.timeIntervalSince(first)
            return duration <= tickDuration
        }
        .filter { $0 }
        .map { _ in }
}

Вышеизложенное изолирует нашу логику и, что не случайно, легко проверить с помощью RxTests. Теперь мы можем заключить его в UIView (это было бы трудно проверить).

func observeLongPress(with minimumPressDuration: Double = 1) -> Observable<Void> {

    let touchesBeganEvent = rx.methodInvoked(#selector(touchesBegan)).map { _ in }
    let touchesEndedEvents = [#selector(touchesEnded), #selector(touchesCancelled)]
        .map(rx.methodInvoked)
    let touchesEndedEvent = Observable.merge(touchesEndedEvents).map { _ in }
    return filterDuration(first: touchesBeganEvent, second: touchesEndedEvent, duration: minimumPressDuration, scheduler: MainScheduler.instance)
}

Вот, пожалуйста. Один пользовательский оператор.

...