Zappel
Zappel

Reputation: 1751

Binding UIKit with view model using ReactiveCocoa 3/4

I have worked a lot with ReactiveCocoa 2.x.y and am now playing with migrating to 4.0 (I know it's still in alpha as of this writing).

However, I have a difficult time figuring out how do this:

RAC(viewModel, selectedDate) = [[self.view.datePicker rac_signalForControlEvents:UIControlEventsValueChanged] map:^id(UIDatePicker *picker) {
    return picker.date
}];

in RC 3 or 4 using Swift. It is as if they haven't yet made the appropriate extensions to UIKit.

I then figured I could maybe do something á la

viewModel.selectedDate <~ view.datePicker.rac_signalForControlEvents(.ValueChanged).toSignalProducer().map({ (x) -> NSDate in
    guard let datePicker = x as? UIDatePicker else { return NSDate() }
    return datePicker.date
})

where the view model has this property:

var selectedDate: MutableProperty<NSDate>

but that gives all kinds of compiler errors:

Binary operator '<~' cannot be applied to operands of type 'MutableProperty<NSDate>' and 'SignalProducer<NSDate, NSError>'

EDIT

So I have managed to get rid of compiler errors using this:

viewModel.selectedDate <~ _mainView.datePicker.rac_signalForControlEvents(.ValueChanged).toSignalProducer()
.flatMapError({ (error) -> SignalProducer<AnyObject?, NoError> in
    return .empty
}).map({ (x) -> NSDate in
    guard let datePicker = x as? UIDatePicker else { return NSDate() }
    return datePicker.date
})

but none of this code is ever executed. It seems like the producer is not started correctly, since this does get executed:

_mainView.datePicker.rac_signalForControlEvents(.ValueChanged).toSignalProducer()
.flatMapError({ (error) -> SignalProducer<AnyObject?, NoError> in
    return .empty
}).map({ (x) -> NSDate in
    guard let datePicker = x as? UIDatePicker else { return NSDate() }
    return datePicker.date
}).startWithNext({ (date) -> () in
    print("\(date)")
})

EDIT 2

To make sure that everything else is working as intended I have this:

_mainView.datePicker.rac_signalForControlEvents(.ValueChanged).toSignalProducer().startWithNext { (x) -> () in
    guard let datePicker = x as? UIDatePicker else { return }
    print("\(datePicker.date)")
}

which prints out date changes just fint.

EDIT 3

Further, to make sure the view model property is also fine, I have this:

viewModel.selectedDate.producer.startWithNext { (selectedDate) -> () in
    print("Selected date: \(selectedDate)")
}

viewModel.selectedDate = MutableProperty(NSDate())

which also prints as expected.

EDIT 4

My view model looks as follows:

class MainViewModel {
    var selectedDate: MutableProperty<NSDate>

    init() {
        selectedDate = MutableProperty(NSDate())
    }
}

And my view controller:

private var viewModel = MainViewModel()

Upvotes: 1

Views: 708

Answers (1)

NachoSoto
NachoSoto

Reputation: 1743

The reason for the error is because you can't bind a producer that can emit errors, you need to handle them first. Because this is a signal converted from RACSignal there is no way of knowing if they can really be emitted, so what I would do is guard against that:

viewModel.selectedDate <~ view.datePicker
       .rac_signalForControlEvents(.ValueChanged)
       .toSignalProducer()
       .map { x -> NSDate in
            guard let datePicker = x as? UIDatePicker else { return NSDate() }
            return datePicker.date
       }
       .flatMapErrors { error -> SignalProducer<NSDate, NoError> in
           fatalError("Unexpected error: \(error)")

           return .empty
     }

I'd recommend creating an operator in a SignalProducerType extension to implement this, something like assumeNoErrors.

Upvotes: 0

Related Questions