对 MVVM 中的信号做出反应?

Reacting to a Signal in MVVM?

我第一次设置登录视图和使用响应式编程时 - 我无法从我的 ViewContoller 生成一个信号,该信号将在我的视图中使用包含我的表单验证的 Bool 提醒我的 observeValues 侦听器:

查看文件

viewModel.outputs.loginSuccess
    .observeValues { [weak self] value in
        print(value)
}

使用我当前的代码 loginSuccess 每次更改电子邮件或密码文本字段时都会触发(我的视图文件中有 .addTarget 用于更新我的模型视图文件中的 MutableProperites) .我遇到麻烦的地方是为 tryLogin 创建一个信号,该信号仅在按下登录按钮时发出,然后映射我的表单验证( emailAndPassword.map(isValid) ),我可以在我的视图文件中对其进行响应。

模型视图文件

import ReactiveCocoa
import ReactiveSwift
import Result

public protocol LoginViewModelInputs {

    /// String value of email textfield text
    func emailChanged(_ email: String?)

    /// String value of password textfield text
    func passwordChanged(_ password: String?)

    /// Call when login button is pressed.
    func loginButtonTapped()
}

public protocol LoginViewModelOutputs {

    /// Emits on login.
    var loginSuccess: Signal<(Bool), NoError> { get }
}

public protocol LoginViewModelType {
    var inputs: LoginViewModelInputs { get }
    var outputs: LoginViewModelOutputs { get }
}

public final class LoginViewModel: LoginViewModelType, LoginViewModelInputs,     LoginViewModelOutputs {

    public init() {

        let emailAndPassword = Signal.combineLatest(
            self.emailChangedProperty.signal.skipNil(),
            self.passwordChangedProperty.signal.skipNil()
        )

        let tryLogin = loginButtonTappedProperty.map {
            emailAndPassword.map(isValid)
        }

        self.loginSuccess = tryLogin.value
    }

    fileprivate let emailChangedProperty = MutableProperty<String?>(nil)
    public func emailChanged(_ email: String?) {
        self.emailChangedProperty.value = email
    }
    fileprivate let loginButtonTappedProperty = MutableProperty()
    public func loginButtonTapped() {
        self.loginButtonTappedProperty.value = ()
    }
    fileprivate let passwordChangedProperty = MutableProperty<String?>(nil)
    public func passwordChanged(_ password: String?) {
        self.passwordChangedProperty.value = password
    }

    public let loginSuccess: Signal<(Bool), NoError>

    public var inputs: LoginViewModelInputs { return self }
    public var outputs: LoginViewModelOutputs { return self }
}

func isValid(email: String, password: String) -> Bool {
    return !email.characters.isEmpty && !password.characters.isEmpty
}

感谢任何帮助。我没有找到太多好的文档来了解信号或观察者,但我可能没找对地方。

Here 是使用 RAC 构建的输入表单的示例。

它有点过时了——本例中以 rex_ 开头的所有内容(它是 UI 绑定的外部扩展库,现在直接集成到 RAC 中)现在更改为.reactive.

  • UITextField.rex_textSignal 现在是 UITextField.reactive.continuousTextValues
  • UIButton.rex_pressed 现在是 UIButton.reactive.pressed

您无需将凭据保存到 authenticationAction 中的 NSUserDefaults,而是执行实际的身份验证操作。

顺便说一句: 你不应该将用户凭证,尤其是密码保存到 NSUserDefaults。请改用钥匙串!

关键点是

  • 在您的 viewModel 中使用 MutableProperty 来存储当前输入值
  • 在界面元素上使用 .reactive 属性 和绑定运算符 <~ 将您的 viewModel 绑定到输入 (viewModel.attribute <~ textField.reactive.continuouseTextValues)
  • 使用Action按需执行实际工作
  • 通过 CocoaAction (button.reactive.pressed = CocoaAction(viewModel.loginAction))
  • Action 绑定到 UI 控件

希望对你有帮助!

这是我会采用的方法

  • 根据用户名和密码输入的验证启用和禁用登录按钮
  • 将登录动作绑定到按钮事件

代码如下:

let usernameField: UITextField
let passwordField: UITextField
let loginEnabled: MutableProperty<Bool>
let loginButton: UIButton

loginEnabled <~
    Signal.combineLatest(
        usernameField.reactive.controlEvents(.allEditingEvents),
        passwordField.reactive.controlEvents(.allEditingEvents))
    .map { _ -> Bool in
        return validation()
    }

// only allow button to be pressed when validation succeeds
loginButton.reactive.isEnabled <~ loginEnabled

// login action
let loginAction = Action<(String?,String?),Void,NoError> { (username, password) -> SignalProducer<Void, NoError> in
    // replace with login procedure
    return SignalProducer.empty
}

// bind button event to login action
loginButton.reactive.pressed = CocoaAction(loginAction, input: 
    (usernameField.text, passwordField.text))