Reactive Cocoa / Reactive Swift - Swift 3.0 missing methods
Some parts of the Obj-C API have been divided in another framework : ReactiveObjC.
I needed to install this framework to access these methods.
Solution :
As stated in README (Objective-C and Swift section), those Objective-C
API are splitted out to ReactiveObjC framework. You need to add
https://github.com/ReactiveCocoa/ReactiveObjC as a submodule, link the
framework, then import ReactiveObjC.
Please see the following discussion on the issue :
https://github.com/ReactiveCocoa/ReactiveCocoa/issues/3197
RACObserve doesn't give a value. (Reactive Cocoa with Swift)
According to Apple Documentation
When you assign a default value to a stored property, or set its initial value within an initializer, the value of that property is set directly, without calling any property observers.
Reactive observers work on KVO. I guess that is the reason why your observer is not getting called.
How to make basic bindings in ReactiveCocoa 3 and 4
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 aSignalProducer<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 someUIKit
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.
How to combine three Signals using reactive cocoa in swift?
You can use Signal.merge
for that, but first you have to convince the Compiler that you actually want to merge those different types
let p1 = MutableProperty<Int>(1)
let p2 = MutableProperty<Bool>(false)
let p3 = MutableProperty<String>("")
let s1: Signal<Any, NoError> = p1.signal.map { $0 }
let s2: Signal<Any, NoError> = p2.signal.map { $0 }
let s3: Signal<Any, NoError> = p3.signal.map { $0 }
let merged = Signal<Any, NoError>.merge(s1, s2, s3)
merged.observeValues { print($0) }
p1.value = 2
p1.value = 3
p3.value = "Hello"
p2.value = true
Without the map
, the compiler will complain
error: cannot convert value of type 'Signal<Int, NoError>' to specified type 'Signal<Any, NoError>'
Form validation in reactive cocoa
Here's an example implementation of a basic ViewController, I've cleaned up a few of the things that I think could be done better.
class ViewController: UIViewController {
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
func nameValidation(for field: UITextField) -> Signal<Bool, NoError> {
return field
.reactive
.continuousTextValues
.skipNil()
.map { $0.characters.count > 3 }
}
override func viewDidLoad() {
super.viewDidLoad()
let validUserNameSignal = nameValidation(for: nameTextField)
let lastNameFieldSignal = nameValidation(for: lastNameTextField)
let formValidation =
SignalProducer(signal: validUserNameSignal.combineLatest(with: lastNameFieldSignal))
.map { $0 && $1 }
.prefix(value: false)
formValidation.startWithValues {
print($0)
}
}
}
How to use rac_signalForControlEvents
I'm not sure how old the version you're using is, but at some point the library was split into two separate modules:
- ReactiveSwift for the general reactive primitives and operators.
- ReactiveCocoa for reactive integration with Cocoa.
So first make sure you have both of those libraries in your project. Then you can create a control event signal like this:
cell.addButton.reactive.controlEvents(.touchUpInside)
.observeValues { _ in
// Handle the button press here
}
Related Topics
How to Delay a Return-Statement in Swift
Why Does User Defaults Publisher Trigger Multiple Times
Swift Initialization Rule Confusion
How to Convert an Array to List in Realm
How to Calculate The Camera Position from Vuforia Gl Matrix
Dynamic Links Firebase Function Not Being Called at All Xcode 12
Rlmexception', Reason: 'Attempting to Modify Object Outside of a Write Transaction
Can't Dismiss View Controller That's Embedded in a Navigation Controller
Decoding Different Type with and Without Array
How to Grab The Parent Object from a Subview
Can't Set @Ibinspectable Computed Property in UIview
Print Not Working in Swift 3 Extensions
No Trailing Closures Support for Methods with Default Parameter Values
How to Horizontally Center Content of Horizontal Scrollview