我同时是Swift和ReactiveCocoa菜鸟。我想使用MVVM和Reactive Cocoa v3.0-beta.4框架来实现此设置,以学习新RAC 3框架的基础。

我有一个文本字段,我希望文本输入包含3个以上的字母,以进行验证。如果文本通过验证,则应启用下面的按钮。当按钮接收到触地事件时,我想使用 View 模型的属性来触发操作。

由于目前有关RAC 3.0 beta的资源很少,我通过阅读框架Github存储库上的QA来实现以下内容。到目前为止,这是我能想到的:

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

使用此设置,当 View 模型的文本超过3个字符时,将启用该按钮。当用户点击按钮时, View 模型的操作将运行,并且我可以将返回值设为true。到现在为止还挺好。

我的问题是:在 View 模型的 Action 内部,我想使用其存储的text属性并更新代码以使用该属性发出网络请求。因此,我不需要 View Controller 方面的输入。如何而不是要求我的Action属性输入?

最佳答案

ReactiveCocoa/CHANGELOG.md:



因此,当前无法在没有输入的情况下定义Action

我想您可以通过将其设置为AnyObject?并使用便捷初始化程序创建CocoaAction来声明您不关心输入:

cocoaAction = CocoaAction(viewModel.action)

补充说明
  • 我不喜欢使用AnyObject?代替Bool作为validatedTextProducer。我想您更喜欢它,因为绑定(bind)到buttonEnabled属性需要AnyObject?。不过,我宁愿将其强制转换为此处,而不是牺牲 View 模型的类型清晰度(请参见下面的示例)。
  • 您可能希望在 View 模型级别以及UI上限制Action的执行,例如:
    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
                //...
            }
        }
    }
    

    并绑定(bind)到buttonEnabled:
    func bindSignals() {
        buttonEnabled <~ viewModel.action.enabled.producer |> map { $0 as AnyObject }
        //...
    }
    
  • 10-08 08:14
    查看更多