I'm a Swift and ReactiveCocoa noob at the same time. Using MVVM and Reactive Cocoa v3.0-beta.4 framework, I'd like to implement this setup, to learn the basics of the new RAC 3 framework.
I have a text field and I want the text input to contain more than 3 letters, for validation. If the text passes the validation, the button underneath should be enabled. When the button receives the touch down event, I want to trigger an action using the view model's property.
Since there are very few resources about RAC 3.0 beta at the moment, I implemented the following by reading the QAs on the framework's Github repo. Here's what I could come up with so far:
ViewModel.swift
class ViewModel {
var text = MutableProperty<String>("")
let action: Action<String, Bool, NoError>
let validatedTextProducer: SignalProducer<AnyObject?, NoError>
init() {
let validation: Signal<String, NoError> -> Signal<AnyObject?, NoError> = map ({
string in
return (count(string) > 3) as AnyObject?
})
validatedTextProducer = text.producer.lift(validation)
//Dummy action for now. Will make a network request using the text property in the real app.
action = Action { _ in
return SignalProducer { sink, disposable in
sendNext(sink, true)
sendCompleted(sink)
}
}
}
}
ViewController.swift
class ViewController: UIViewController {
private lazy var txtField: UITextField = {
return createTextFieldAsSubviewOfView(self.view)
}()
private lazy var button: UIButton = {
return createButtonAsSubviewOfView(self.view)
}()
private lazy var buttonEnabled: DynamicProperty = {
return DynamicProperty(object: self.button, keyPath: "enabled")
}()
private let viewModel = ViewModel()
private var cocoaAction: CocoaAction?
override func viewDidLoad() {
super.viewDidLoad()
view.setNeedsUpdateConstraints()
bindSignals()
}
func bindSignals() {
viewModel.text <~ textSignal(txtField)
buttonEnabled <~ viewModel.validatedTextProducer
cocoaAction = CocoaAction(viewModel.action, input:"Actually I don't need any input.")
button.addTarget(cocoaAction, action: CocoaAction.selector, forControlEvents: UIControlEvents.TouchDown)
viewModel.action.values.observe(next: {value in
println("view model action result \(value)")
})
}
override func updateViewConstraints() {
super.updateViewConstraints()
//Some autolayout code here
}
}
RACUtilities.swift
func textSignal(textField: UITextField) -> SignalProducer<String, NoError> {
return textField.rac_textSignal().toSignalProducer()
|> map { $0! as! String }
|> catch {_ in SignalProducer(value: "") }
}
With this setup, the button gets enabled when the view model's text is longer than 3 characters. When the user taps on the button, the view model's action runs and I can get the return value as true. So far so good.
My question is: Inside the view model's action, I want to use its stored text property and update the code to make a network request using it. So, I don't need an input from the view controller's side. How can I not require an input for my Action property?
From the ReactiveCocoa/CHANGELOG.md:
So currently there is no way to define an
Action
without an input.I suppose you could declare that you don't care about input by making it
AnyObject?
and creatingCocoaAction
with convenience initialiser:Additional remarks
I dont't like using
AnyObject?
instead ofBool
forvalidatedTextProducer
. I suppose you preferred it because binding to thebuttonEnabled
property requiresAnyObject?
. I would rather cast it there though, instead of sacrificing type clarity of my view model (see example below).You might want to restrict execution of the
Action
on the view model level as well as UI, e.g.:And binding to
buttonEnabled
:If you take a look at Colin Eberhardt blog post on ReactiveCocoa 3 there's a very nice approach to this problem.
Basically because it's still in beta there's no extension on
UIView
that makes those properties easy to use with RAC3 but you can add them easily. I would recommend adding aUIKit+RAC3.swift
extension and adding them as you need:That way you simply replace the
RAC = RACObserve
logic by (for example):Again this is all taken from his blog post which far more descriptive than this answer. I highly recommend anyone interested in using RAC 3 reads his amazing posts and tutorials: