ReactiveCocoa / ReactiveSwift

Streams of values over time

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

SignalProducer of sequence delay between elements

FranDepascuali opened this issue · comments

commented

Description

Hello! Thanks for this awesome library! I've been trying to achieve the following case with producers:
I have a producer that emits events (array). If it's just a single event, send it over, if not, send each element of the array after a delay.

The application of this is to show some toasts in screen. Check this gif:
output

Code

struct ToastEventsPresenter: ToastEventsPresenterType {
    let toasts: SignalProducer<Int, NoError>
    
    init() {
       let someProducer = SignalProducer<[Int], NoError>

       toasts = someProducer
                     .flatten()
                     .skipRepeats()
    }
}

And in consumer

toastPresenter
            .toasts
            .throttle(2, on: QueueScheduler())
            .observe(on: UIScheduler())

With that code I send all the values and get the latest every 2 seconds.

What I would like to do is to emit each value of the array if it has more than one element with a delay. So I tried to do it with flatMap and delay:

struct ToastEventsPresenter: ToastEventsPresenterType {
    let toasts: SignalProducer<Int, NoError>
    
    init() {
       let someProducer = SignalProducer<[Int], NoError>

       toasts = someProducer
                         .flatMap(.concat, { toastEvents -> SignalProducer<Int, NoError> in
                             if toastEvents.count == 1, let toast = toastEvents.first {
                                 return SignalProducer(value: toast)
                             }
                             return SignalProducer(toastEvents).delay(2, on: QueueScheduler())
                         })
                         .skipRepeats()
    }
}
  1. Now, this doesn't work like I expected. When it is an array, the SignalProducer sends the values inmediately and completes, which I don't understand why (I thought that adding the .delay would delay each value and completed of that producer). So the 1) question is why that doesn't work? (Note that the initializer use is SignalProducer(sequence)

  2. Now, even though the delay would work, it is not exactly what I need. I want to only delay the values after the first one, but not the first one. How can I achieve this?

  3. Given that 1) and 2) are answered, how could I achieve a delay between the elements based on a property of the element (and not a constant delay)?

Can you clarify what behavior you want? An example might be helpful. I'm not sure what you're trying to achieve.

commented

@mdiep Thanks! Basically, I want to send toasts. Those toasts can either be a single toast or an array of toasts.

Consider this:

struct Toast {
    let duration: NSTimeInterval
    ...
}

let inputProducer: SignalProducer<[Toast], NoError>

I would like to emit every value of the inputProducer with a timing based on the duration property of the Toast.

outputProducer: SignalProducer<Toast, NoError>

Example:

let toast1 = Toast(duration: 3)
let toast2 = Toast(duration: 5)
let toast3 = Toast(duration: 2)
let inputProducer = SignalProducer([Toast 1, Toast 2, Toast 3])
outputProducer should emit: Toast 1 -- 3 seconds -- Toast 2 --- 5 seconds --- Toast 3 --- 2

hello,

maybe something like this:

struct Toast {
    let duration: TimeInterval
}

let (o, i) = Signal<[Toast], NoError>.pipe()

o.producer
    .flatten()
    .flatMap(.concat) { toast -> SignalProducer<Toast, NoError> in
        SignalProducer(value: toast)
            .concat(SignalProducer.empty
                .delay(toast.duration, on: QueueScheduler.main))
}
    .on(value: { toast in
        // hide previous toast and show the new one
    })
    .startWithCompleted {
        // hide last toast
    }

i.send(value: [Toast(duration: 3), Toast(duration: 1), Toast(duration: 2)])
i.send(value: [Toast(duration: 1), Toast(duration: 3), Toast(duration: 4)])
i.sendCompleted()

Hope this is close to what you want to achieve. :-)

maybe something like this:

That's what I'd do. 👍 Except I'd probably move showing/hiding into the flatMap(.concat).

enum Action {
  case show
  case hide
}

o.producer()
  .flatten()
  .flatMap(.concat) { toast -> SignalProducer<(Action, Toast), NoError> in
    SignalProducer(value: (.show, toast))
      .contact(SignalProducer(value: (.hide, toast)).delay(toast.duration, on: QueueScheduler.main))
  }
  .observeValues { (action, toast) in
    // show or hide toast
  }
commented

Thank you very much @mdiep @TimPapler !!