I've been reading up on ReactiveCocoa v3 lately and I'm struggling with just setting up basic stuff. I've already read the changelog, the tests, the few SO questions and the articles by Colin Eberhardt on the subject. However, I'm still missing examples on basic bindings.
Let's say I have an app that presents the menu of the day. The app is using RAC3 and the MVVM pattern.
Model (Menu)
The model has one simple method for fetching todays menu. As for now, this don't do any network requests, it basically just creates a model object. The mainCourse
property is a String
.
class func fetchTodaysMenu() -> SignalProducer<Menu, NoError> {
return SignalProducer {
sink, dispoable in
let newMenu = Menu()
newMenu.mainCourse = "Some meat"
sendNext(sink, newMenu)
sendCompleted(sink)
}
}
ViewModel (MenuViewModel)
The view model exposes different String
variables for letting the view controller show the menu. Let's just add one property for showing the main course.
var mainCourse = MutableProperty("")
And we add a binding for this property:
self.mainCourse <~ Menu.fetchTodaysMenu()
|> map { menu in
return menu.mainCourse!
}
ViewController (MenuViewController)
Last but not least, I want to present this main course in a view. I'll add a UILabel
for this.
var headline = UILabel()
And finally I want to set the text
property of that UILabel by observing my view model. Something like:
self.headline.text <~ viewModel.headline.producer
Which unfortunately does not work.
Questions
fetchTodaysMenu()
returns a SignalProducer<Menu, NoError>
, but what if I want this method to return a SignalProducer<Menu, NSError>
instead? This would make my binding in my view model fail as the method now may return an error. How do I handle this?MutableProperty
that represents the text
property of the UILabel
, but I never got it right. I also think it feels clumsy or verbose to have to create extra variables for each property I want to bind. This was not needed in RAC2. I intentionally also tried to avoid using DynamicProperty
, but maybe I shouldn't? I basically just want to find the right way of doing RAC(self.headline, text) = RACObserve(self.viewModel, mainCourse);
.Any other feedback/guidance on how to make this basic setup is highly appreciated.
So, after writing this question Colin Eberhardt made a part 3 of his RAC3 blog post series which includes a interesting and very relevant example of using MVVM and RAC3. The post can be found here and the source code here.
Based on his work, I've managed to answer my own questions:
By taking a slightly different approach, I'm able to make the fetchTodaysMenu()
return a SignalProducer<Menu, NSError>
as wanted. Here's how what I then would do in my view model:
MenuService.fetchTodaysMenu()
|> observeOn(QueueScheduler.mainQueueScheduler)
|> start(next: { response in
self.mainCourse.put(response.mainCourse!)
}, error: {
println("Error \($0)")
})
It seems like there's no UIKit
bindings yet as of RAC3 beta 4. Colin made some UIKit
extensions himself to help him make these bindings I was looking for as well. These can be found here. Adding them to my project, made be able to do exactly what I wanted to:
self.mainCourse.rac_text <~ self.viewModel.mainCourse
Update May 25, 2015
After been working a lot more with ReactiveCocoa 3, I would like to answer 1) once again. By using catch
, it's possible to do this in a more declarative manner. I ended up implementing a small helper function for this:
public func ignoreError<T: Any, E: ErrorType>(signalProducer: SignalProducer<T, E>) -> SignalProducer<T, NoError> {
return signalProducer
|> catch { _ in
SignalProducer<T, NoError>.empty
}
}
The function transforms any NSError
to NoError
making it possible to bind as I wanted to by doing MenuService.fetchTodaysMenu() |> ignoreError
.
I open sourced my project as this might be a good starting point for others looking into ReactiveCocoa 3.0: https://github.com/s0mmer/TodaysReactiveMenu
Update March 5, 2016
As highlighted in the comments, since Swift 2, the ignoreError
function would now look like:
public func ignoreError() -> SignalProducer<Value, NoError> {
return flatMapError { _ in
SignalProducer<Value, NoError>.empty
}
}
Also, an extension library called Rex has also been made, where something similar has been added.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With