2

When doing forms with fields i want to send if there is a change i often do

let initialOrChangedName = Signal.merge(
   nameChanged.signal, 
   self.viewDidLoadProperty.signal
       .map { _ in nil }  
   )

where

private let nameChangedProperty = MutableProperty<String?>(nil)
private let viewDidLoadProperty = MutableProperty(())

to get a signal that has fired once on load, so i can use it in a combineLatest when user taps a button that will fire a web request with the form value to server. Since this signal merges it will give all values that change after the initial value, allowing me to send the newest value when user taps the submit button

Usage for this is usually something like

Signal.combineLatest(intialOrChangedName, initialOrChangedAge)
.sample(on:sendButtonTappedProperty.signal)

if values sent nil, i just dont include them in the web request, but i do get the other values if some of them was changed by user.

Since this is a fairly common pattern, i want to generalize it to a single function, for example

let initialOrChangedName = nameChanged.initialOrChangedState(on: viewDidLoadProperty)

I've tried writing it

extension MutableProperty where Value: OptionalProtocol {


     public func initialOrChangedState(on viewDidLoadProperty: MutableProperty<Void>) -> Signal<Value?, Error> {

          return Signal.merge(self.signal.map(Optional.init),
               viewDidLoadProperty.signal.map { _ in nil})
     }
}

Which looks good on paper, but will return String?? for the example given, and does not work.

I've also tried writing it as a static function on Signal, but with no luck.

bogen
  • 9,954
  • 9
  • 50
  • 89

2 Answers2

0

I guess it should be something like this:

extension MutableProperty where Value: OptionalProtocol {


         public func initialOrChangedState(on viewDidLoadProperty: MutableProperty<Void>) -> Signal<Value?, Error> {

              return self.signal.map({Optional<Value>($0)}).merge(with: viewDidLoadProperty.signal.map({_ in nil}))
         }
    }

But, whats the point of using viewDidLoadProperty? Actually, if you subscribe to your signal at the end of viewDidLoad(), you don't even need such a property and as a result you wont need that merge() thing and you wont need to extend MutableProperty protocol.

So, all you need to do is something like:

submitButton.reactive.controlEvents(.touchUpInside).observer(on: UIScheduler()).observeValues({ _ in
   readInputs()
   launchRequest()
})
Alchi
  • 799
  • 9
  • 19
  • 1
    We follow the input/output methodology (used for example by the open source kickstarter-app), so every "rule" for what will be sent in a request is set up in the ViewModel init(). We can use viewDidLoadProperty as a trigger for other outputs – bogen Sep 12 '18 at 13:01
  • Also it did not compile the self.signal.map(self.value), it says no map candidates produce the contextual result type Signal<_?, MutableProperty.Error> aka Signal, NoError> – bogen Sep 12 '18 at 13:13
  • also check out this: http://reactivecocoa.io/reactiveswift/docs/latest/Classes/Property.html#/s:13ReactiveSwift8PropertyCACyxGx7initial_AA14SignalProducerVyx6Result7NoErrorOG4thentcfc – Alchi Sep 12 '18 at 16:07
  • Problem is that when i add the initialOrChangedState to a property that has an optional, the return type is String?? – bogen Sep 14 '18 at 11:05
0

I might be misunderstanding so forgive me, if I am. But I think something like this might help. I have this method in a library I’ve written.

public func to<T>(_ value: T) -> Signal<T, Error> {
    self.map { _ in value }
}

which allows you to do something like this

let voidProperty = MutableProperty(())
let nilStringSignal: Signal<String?, Never> = voidProperty.signal.to(nil)

So then maybe your case could be something like this, which leans a bit on type inference

nameChanged.signal.merge(with: self.viewDidLoadProperty.signal.to(nil))

I know maybe that’s not quite as concise as you want. Working with generics like optionals in signals can sometimes make the type wrangling a bit frustrating