// ViewModel
class GithubSignupViewModel1 {
// outputs {
let validatedUsername: Observable<ValidationResult>
let validatedPassword: Observable<ValidationResult>
let validatedPasswordRepeated: Observable<ValidationResult>
// Is signup button enabled
let signupEnabled: Observable<Bool>
// Has user signed in
let signedIn: Observable<Bool>
// Is signing process in progress
let signingIn: Observable<Bool>
// }
init(input: (
username: Observable<String>,
password: Observable<String>,
repeatedPassword: Observable<String>,
loginTaps: Observable<Void>
),
dependency: (
API: GitHubAPI,
validationService: GitHubValidationService,
wireframe: Wireframe
)
) {
let API = dependency.API
let validationService = dependency.validationService
let wireframe = dependency.wireframe
/**
Notice how no subscribe call is being made.
Everything is just a definition.
Pure transformation of input sequences to output sequences.
*/
validatedUsername = input.username
.flatMapLatest { username in
return validationService.validateUsername(username)
.observeOn(MainScheduler.instance)
.catchErrorJustReturn(.failed(message: "Error contacting server"))
}
.share(replay: 1)
validatedPassword = input.password
.map { password in
return validationService.validatePassword(password)
}
.share(replay: 1)
validatedPasswordRepeated = Observable.combineLatest(input.password, input.repeatedPassword, resultSelector: validationService.validateRepeatedPassword)
.share(replay: 1)
let signingIn = ActivityIndicator()
self.signingIn = signingIn.asObservable()
let usernameAndPassword = Observable.combineLatest(input.username, input.password) { ($0, $1) }
signedIn = input.loginTaps.withLatestFrom(usernameAndPassword)
.flatMapLatest { (username, password) in
return API.signup(username, password: password)
.observeOn(MainScheduler.instance)
.catchErrorJustReturn(false)
.trackActivity(signingIn)
}
.flatMapLatest { loggedIn -> Observable<Bool> in
let message = loggedIn ? "Mock: Signed in to GitHub." : "Mock: Sign in to GitHub failed"
return wireframe.promptFor(message, cancelAction: "OK", actions: [])
// propagate original value
.map { _ in
loggedIn
}
}
.share(replay: 1)
signupEnabled = Observable.combineLatest(
validatedUsername,
validatedPassword,
validatedPasswordRepeated,
signingIn.asObservable()
) { username, password, repeatPassword, signingIn in
username.isValid &&
password.isValid &&
repeatPassword.isValid &&
!signingIn
}
.distinctUntilChanged()
.share(replay: 1)
}
}
// ViewController
class GitHubSignupViewController1 : ViewController {
@IBOutlet weak var usernameOutlet: UITextField!
@IBOutlet weak var usernameValidationOutlet: UILabel!
@IBOutlet weak var passwordOutlet: UITextField!
@IBOutlet weak var passwordValidationOutlet: UILabel!
@IBOutlet weak var repeatedPasswordOutlet: UITextField!
@IBOutlet weak var repeatedPasswordValidationOutlet: UILabel!
@IBOutlet weak var signupOutlet: UIButton!
@IBOutlet weak var signingUpOulet: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
let viewModel = GithubSignupViewModel1(
input: (
username: usernameOutlet.rx.text.orEmpty.asObservable(),
password: passwordOutlet.rx.text.orEmpty.asObservable(),
repeatedPassword: repeatedPasswordOutlet.rx.text.orEmpty.asObservable(),
loginTaps: signupOutlet.rx.tap.asObservable()
),
dependency: (
API: GitHubDefaultAPI.sharedAPI,
validationService: GitHubDefaultValidationService.sharedValidationService,
wireframe: DefaultWireframe.shared
)
)
// bind results to {
viewModel.signupEnabled
.subscribe(onNext: { [weak self] valid in
self?.signupOutlet.isEnabled = valid
self?.signupOutlet.alpha = valid ? 1.0 : 0.5
})
.disposed(by: disposeBag)
viewModel.validatedUsername
.bind(to: usernameValidationOutlet.rx.validationResult)
.disposed(by: disposeBag)
viewModel.validatedPassword
.bind(to: passwordValidationOutlet.rx.validationResult)
.disposed(by: disposeBag)
viewModel.validatedPasswordRepeated
.bind(to: repeatedPasswordValidationOutlet.rx.validationResult)
.disposed(by: disposeBag)
viewModel.signingIn
.bind(to: signingUpOulet.rx.isAnimating)
.disposed(by: disposeBag)
viewModel.signedIn
.subscribe(onNext: { signedIn in
print("User signed in \(signedIn)")
})
.disposed(by: disposeBag)
//}
let tapBackground = UITapGestureRecognizer()
tapBackground.rx.event
.subscribe(onNext: { [weak self] _ in
self?.view.endEditing(true)
})
.disposed(by: disposeBag)
view.addGestureRecognizer(tapBackground)
}
}