Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Max Alexander: MVVM with RxSwift

Realm
December 15, 2016

Max Alexander: MVVM with RxSwift

Abstract/excerpt: MVVM is the critical design pattern for front-end engineers. There are so many ways that objects can talk to each other in an iOS App: delegates, callbacks, notification. Max will show you how to streamline your development process in 3 easy patterns with RxSwift. He’ll go over the MVVM basics, creating custom observers, wrangling disparate APIs, and manipulating calls using concurrency and dispatch_queues. Your code will be so neat that you could quit your job and leave it for the next guy in impeccable condition.

Bio: Max Alexander has been a contract iOS Developer for the last 2 years and recently has been the iOS Engineer for Hipmunk. He deeply loves reactive apps and always loves to evangelize teams to use RxJS, RxSwift, and RxJava whenever possible. He has a particular interest in Operational Transform and Offline-First application development On his leisure time Max contributes to Github Projects, develops games on Unreal Engine 4 and Unity.

Twitter: https://twitter.com/maxofeden

Realm

December 15, 2016
Tweet

More Decks by Realm

Other Decks in Technology

Transcript

  1. class LoginViewController : UIViewController { @IBOutlet weak var usernameTextField: UITextField!

    @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var confirmButton: UIButton } Code So Far
  2. struct LoginViewModel { var username: String = "" var password:

    String = "" func attemptToLogin() { let params = [ "username": username, "password": password ] ApiClient.shared.login(email: email, password: password) { (response, error) in } } }
  3. class LoginViewController : UIViewController { @IBOutlet weak var usernameTextField: UITextField!

    @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var confirmButton: UIButton //viewModel is just a member variable here. var viewModel = LoginViewModel() override func viewDidLoad(){ super.viewDidLoad() } }
  4. class LoginViewController : UIViewController { @IBOutlet weak var usernameTextField: UITextField!

    @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var confirmButton: UIButton //viewModel is just a member variable here. var viewModel = LoginViewModel() override func viewDidLoad(){ super.viewDidLoad() usernameTextField.addTarget(self, action: #selector(LoginViewController.usernameTextFieldDidChange:_), forControlEvents: UIControlEvents.EditingChanged) passwordTextField.addTarget(self, action: #selector(LoginViewController.passworldTextFieldDidChange:_), forControlEvents: UIControlEvents.EditingChanged) } func usernameTextFieldDidChange(textField: UITextField){ viewModel.username = textField.text ?? "" } func passworldTextFieldDidChange(textField: UITextField){ viewModel.password = textField.text ?? "" } func confirmButtonTapped(sender: UIButton){ viewModel.attemptLogin() } }
  5. struct LoginViewModel { var username: String = "" { didSet

    { evaluateValidity() } } var password: String = "" { didSet { evaluateValidity() } } var isValid : Bool = "" func attemptToLogin() { let params = [ "username": username, "password": password ] ApiClient.shared.login(email: email, password: password) { (response, error) in } } private func evaluateValidity(){ isValid = username.characters.count > 0 && password.characters.count > 0 } }
  6. struct LoginViewModel { var username: String = "" { didSet

    { evaluateValidity() } } var password: String = "" { didSet { evaluateValidity() } } var isValid : Bool = "" { didSet { loginViewController?.confirmButton.isDisabled = !isValid } } weak var loginViewController : LoginViewController? func attemptToLogin() { //truncated for space } private func evaluateValidity(){ isValid = username.characters.count > 0 && password.characters.count > 0 } } Never do this!
  7. View Model DONTS Reference View Controller Don't import UIKit Reference

    anything from UIKit Data should be going in and out
  8. struct LoginViewModel { var username: String = "" { didSet

    { evaluateValidity() } } var password: String = "" { didSet { evaluateValidity() } } var isValid : Bool = "" { didSet { isValidCallback?(isValid: isValid) } } var isValidCallback : ((isValid: Bool) -> Void)? func attemptToLogin() { //truncated for space } private func evaluateValidity(){ isValid = username.characters.count > 0 && password.characters.count > 0 } }
  9. class LoginViewController { @IBOutlet var confirmButton: UIButton! var loginViewModel =

    LoginViewModel() override func viewDidLoad(){ super.viewDidLoad() loginViewModel.isValidCallback = { [weak self] (isValid) in self?.confirmButton.isEnabled = isValid } } } Listen to the Callback
  10. import RxSwift struct LoginViewModel { var username = Variable<String>("") var

    password = Variable<String>("") var isValid : Observable<Bool>{ return Observable.combineLatest( self.username, self.password) { (username, password) in return username.characters.count > 0 && password.characters.count > 0 } } }
  11. import RxSwift import RxCocoa class LoginViewController { var usernameTextField: UITextField!

    var passwordTextField: UITextField! var confirmButton: UIBUtton! var viewModel = LoginViewModel() var disposeBag = DisposeBag() override func viewDidLoad(){ super.viewDidLoad() usernameTextField.rx.text.bindTo(viewModel.username).addTo(disposeBag) passwordTextField.rx.text.bindTo(viewModel.password).addTo(disposeBag) //from the viewModel viewModel.rx.isValid.map{ $0 } .bindTo(confirmButton.rx.isEnabled) } }
  12. class ViewController : UIViewController { override func viewDidLoad(){ super.viewDidLoad() let

    isValid Observable.combineLatest( username.rx.text, password.rx.text, resultSelector: { (username, password) -> Bool in return username.characters.count > 0 && password.characters.count > 0 }) isValid.bindTo(confirmButton.rx.isEnabled) .addTo(disposeBag) } } ViewController is still massive
  13. class MyCustomView : UIView { var sink : AnyObserver<SomeComplexStructure> {

    return AnyObserver { [weak self] event in switch event { case .next(let data): self?.something.text = data.property.text break case .error(let error): self?.backgroundColor = .red case .completed: self.alpha = 0 } } } } What if RxSwift + RxCocoa doesn't have bindings
  14. class ViewController { let myCustomView : MyCustomView override func viewDidLoad(){

    super.viewDidLoad() viewModel.dataStream .bindTo(myCustomView.sink) .addTo(disposeBag) } } Custom Binding Sinks