Skip to content

Instantly share code, notes, and snippets.

@seyhunak
Created June 5, 2016 16:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seyhunak/92991a548d67e5287131ed6f625735fe to your computer and use it in GitHub Desktop.
Save seyhunak/92991a548d67e5287131ed6f625735fe to your computer and use it in GitHub Desktop.
Yet another GitHub sign in screen using MVVM
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let viewModel = LoginViewModel(provider: GitHubProvider)
let loginViewController = R.storyboard.main.loginViewController!
loginViewController.viewModel = viewModel
if let window = window {
window.rootViewController = loginViewController
window.makeKeyAndVisible()
}
return true
}
//
// LoginViewController.swift
// Origin
//
// Created by krawiecp on 06/01/2016.
// Copyright © 2016 tailec. All rights reserved.
//
import UIKit
import RxSwift
import RxCocoa
class LoginViewController: UIViewController {
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var loginButton: UIButton!
var viewModel: LoginViewModel!
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
bindToRx()
let tapBackground = UITapGestureRecognizer(target: self, action: Selector("dismissKeyboard"))
view.addGestureRecognizer(tapBackground)
}
private func bindToRx() {
_ = usernameTextField.rx_text.bindTo(viewModel.username)
_ = passwordTextField.rx_text.bindTo(viewModel.password)
_ = loginButton.rx_tap.bindTo(viewModel.loginTaps)
viewModel.loginEnabled.bindTo(loginButton.rx_enabled).addDisposableTo(disposeBag)
viewModel.loginExecuting.subscribeNext { executing in
UIApplication.sharedApplication().networkActivityIndicatorVisible = executing
}
.addDisposableTo(disposeBag)
viewModel.loginFinished.subscribeNext { loginResult in
switch loginResult {
case .Failed(let message):
let alert = UIAlertController(title: "Oops!", message:message, preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Default) { _ in })
self.presentViewController(alert, animated: true, completion: nil)
case .OK:
self.dismissViewControllerAnimated(true, completion: nil)
}
}
.addDisposableTo(disposeBag)
}
func dismissKeyboard() {
view.endEditing(true)
}
}
//
// LoginViewModel.swift
// Origin
//
// Created by krawiecp on 06/01/2016.
// Copyright © 2016 tailec. All rights reserved.
//
import Foundation
import RxSwift
import RxCocoa
import RxViewModel
import Moya
class LoginViewModel: RxViewModel {
// Input
var username = Variable("")
var password = Variable("")
var loginTaps = PublishSubject<Void>()
// Output
let loginEnabled: Observable<Bool>
let loginFinished: Observable<LoginResult>
let loginExecuting: Observable<Bool>
// Private
private let provider: RxMoyaProvider<GitHub>
init(provider: RxMoyaProvider<GitHub>) {
self.provider = provider
let activityIndicator = ActivityIndicator()
loginExecuting = activityIndicator
.asObservable()
.shareReplay(1)
.observeOn(MainScheduler.instance)
loginEnabled = Observable.combineLatest(username.asObservable(), password.asObservable())
{ $0.characters.count > 0 && $1.characters.count > 6 }.debug()
.shareReplay(1)
.observeOn(MainScheduler.instance)
let usernameAndPassword = Observable.combineLatest(username.asObservable(), password.asObservable())
{ ($0, $1) }
loginFinished = loginTaps
.withLatestFrom(usernameAndPassword)
.flatMapLatest { (username, password) -> Observable<LoginResult> in
provider.request(GitHub.Token(username: username, password: password))
.trackActivity(activityIndicator)
.mapJSON()
.doOn(onNext: { json in
var appToken = AppToken()
appToken.token = json["token"] as? String
})
.map { json in
if let message = json["message"] as? String {
return LoginResult.Failed(message: message)
} else {
return LoginResult.OK
}
}
.shareReplay(1)
.observeOn(MainScheduler.instance)
}
super.init()
}
}
import Quick
import Nimble
import RxBlocking
import RxTests
@testable import Origin
class LoginViewModelSpec: QuickSpec {
override func spec() {
describe("view model with") {
var viewModel: LoginViewModel!
beforeEach {
viewModel = LoginViewModel(provider: GitHubProvider)
}
context("valid credentials") {
beforeEach {
viewModel.username.value = "johny"
viewModel.password.value = "yellowpanties"
}
it("enables login") {
expect(try! viewModel.loginEnabled.toBlocking().first()).toEventually(equal(true))
}
}
context("invalid credentials") {
beforeEach {
viewModel.username.value = ""
viewModel.password.value = ""
}
it("disables login") {
expect(try! viewModel.loginEnabled.toBlocking().first()).toEventually(equal(false))
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment