Created
June 5, 2016 16:45
-
-
Save seyhunak/92991a548d67e5287131ed6f625735fe to your computer and use it in GitHub Desktop.
Yet another GitHub sign in screen using MVVM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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() | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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