如何使用ReactiveCocoa 3实现基本的UITextFieldinput+ UIButton操作场景?

我是一个Swift和ReactiveCocoa noob在同一时间。 使用MVVM和Reactive Cocoa v3.0-beta.4框架,我想实现这个设置,学习新的RAC 3框架的基础知识。

我有一个文本字段,我希望文本input包含超过3个字母,用于validation。 如果文本通过validation,则应该启用下面的button。 当button收到触发事件时,我想使用视图模型的属性触发一个动作。

由于目前关于RAC 3.0 beta的资源非常less,我通过阅读框架的Github回购库上的QA来实现以下function。 以下是我可以提出的到目前为止:

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: "") } } 

使用此设置,当视图模型的文本长度超过3个字符时,该button将启用。 当用户点击button,视图模型的动作运行,我可以得到的返回值为true。 到现在为止还挺好。

我的问题是:在视图模型的行动,我想使用其存储的文本属性和更新代码,使用它的networking请求。 所以,我不需要从视图控制器的一方input。 我如何不需要我的行动属性的input?

从ReactiveCocoa / CHANGELOG.md :

一个动作必须指明它所接受的input的types,它产生的输出的types,以及可能发生什么types的错误(如果有的话)。

所以目前没有办法定义没有input的Action

我想你可以声明,你不关心input通过使AnyObject? 和方便初始化创buildCocoaAction

 cocoaAction = CocoaAction(viewModel.action) 

补充说明

  • 我不喜欢使用AnyObject? 而不是Bool for validatedTextProducer 。 我想你喜欢它,因为绑定到buttonEnabled属性需要AnyObject? 。 我宁愿把它扔在那里,而不是牺牲我的视图模型的types清晰度(见下面的例子)。

  • 您可能想限制视图模型级别上的Action执行以及UI,例如:

     class ViewModel { var text = MutableProperty<String>("") let action: Action<AnyObject?, Bool, NoError> // if you want to provide outside access to the property var textValid: PropertyOf<Bool> { return PropertyOf(_textValid) } private let _textValid = MutableProperty(false) init() { let validation: Signal<String, NoError> -> Signal<Bool, NoError> = map { string in return count(string) > 3 } _textValid <~ text.producer |> validation action = Action(enabledIf:_textValid) { _ in //... } } } 

    并绑定到buttonEnabled

     func bindSignals() { buttonEnabled <~ viewModel.action.enabled.producer |> map { $0 as AnyObject } //... } 

如果你看一下ReactiveCocoa 3上的Colin Eberhardt 博客 ,那么这个问题有一个非常好的方法。

基本上,因为它仍然处于testing阶段,所以在UIView上没有任何扩展,使得这些属性很容易与RAC3一起使用,但是您可以轻松添加它们。 我会build议添加一个UIKit+RAC3.swift扩展名,并根据需要添加它们:

 import UIKit import ReactiveCocoa struct AssociationKey { static var hidden: UInt8 = 1 static var alpha: UInt8 = 2 static var text: UInt8 = 3 static var enabled: UInt8 = 4 } func lazyAssociatedProperty<T: AnyObject>(host: AnyObject, key: UnsafePointer<Void>, factory: ()->T) -> T { var associatedProperty = objc_getAssociatedObject(host, key) as? T if associatedProperty == nil { associatedProperty = factory() objc_setAssociatedObject(host, key, associatedProperty, UInt(OBJC_ASSOCIATION_RETAIN)) } return associatedProperty! } func lazyMutableProperty<T>(host: AnyObject, key: UnsafePointer<Void>, setter: T -> (), getter: () -> T) -> MutableProperty<T> { return lazyAssociatedProperty(host, key) { var property = MutableProperty<T>(getter()) property.producer .start(next: { newValue in setter(newValue) }) return property } } extension UIView { public var rac_alpha: MutableProperty<CGFloat> { return lazyMutableProperty(self, &AssociationKey.alpha, { self.alpha = $0 }, { self.alpha }) } public var rac_hidden: MutableProperty<Bool> { return lazyMutableProperty(self, &AssociationKey.hidden, { self.hidden = $0 }, { self.hidden }) } } extension UIBarItem { public var rac_enabled: MutableProperty<Bool> { return lazyMutableProperty(self, &AssociationKey.enabled, { self.enabled = $0 }, { self.enabled }) } } 

这样你就可以简单地用(例如)replaceRAC = RACObserve逻辑:

 var date = MutableProperty<NSDate?>(nil) var time = MutableProperty<Int?>(nil) let doneItem = UIBarButtonItem() doneItem.rac_enabled <~ date.producer |> combineLatestWith(time.producer) |> map { return $0.0 != nil && $0.1 != nil } 

这一切都是从他的博客文章,这比这个答案更具描述性。 我强烈build议任何有兴趣使用RAC 3的人阅读他的惊人post和教程:

  • 首先看看RAC 3
  • 信号产生者和API清晰度
  • MVVM和RAC 3