Reputation: 87
While setting up a login view and using Reactive programming for my first time - I am having trouble generating a Signal from my ViewContoller that will alert my observeValues
listener in my view with a Bool containing my form validation:
View File
viewModel.outputs.loginSuccess
.observeValues { [weak self] value in
print(value)
}
With my current code loginSuccess
is firing every time the email or password text fields are changed (I have .addTarget
's in my View File that update my MutableProperites in My Model View file). Where I am experiencing trouble is creating a signal for tryLogin
that only emits when the login button is pressed, and then maps my form validation ( emailAndPassword.map(isValid)
), which I can respond to in my View File.
Model View File
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
}
Any help is appreciated. I haven't found much good documentation to learn about Signals or Observers, but it's possible I haven't been looking in the right places.
Upvotes: 0
Views: 853
Reputation: 4210
Here is the approach that I would take
Here is the code:
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))
Upvotes: 1
Reputation: 3357
Here is an example of an Input Form built with RAC.
Its a little bit outdated though - everything in this example that starts with rex_
(which was an external extension library for UI Bindings which now is integrated directly into RAC) is now changed to .reactive.
UITextField.rex_textSignal
is now UITextField.reactive.continuousTextValues
UIButton.rex_pressed
is now UIButton.reactive.pressed
Instead of saving the credentials to NSUserDefaults
in the authenticationAction
, you would implement your actual authenticate action.
BTW: you should never save user credentials, especially passwords, to NSUserDefaults
. Use the Keychain instead!
The Key-Points are
MutableProperty
in your viewModel to store the current input values.reactive
property on interface elements and the binding operator <~
to bind your viewModel to the inputs (viewModel.attribute <~ textField.reactive.continuouseTextValues
)Action
to perform the actual work on demandAction
to a UIControl via CocoaAction
(button.reactive.pressed = CocoaAction(viewModel.loginAction)
)Hope that helps you!
Upvotes: 1