Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make basic bindings in ReactiveCocoa 3 and 4

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

  1. The method 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?
  2. As mentioned, the current binding in my view controller does not work. I've been playing around with creating a 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.

like image 322
Steffen D. Sommer Avatar asked May 14 '15 14:05

Steffen D. Sommer


1 Answers

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:

  1. 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)")
        })
    
  2. 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.

like image 178
Steffen D. Sommer Avatar answered Nov 12 '22 01:11

Steffen D. Sommer