Maximilian Alexander
View This Live with Me!
class LoginViewController : UIViewController {
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var confirmButton: UIButton
}
The Model is just an abstract word for
"your data"
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
}
}
}
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()
}
}
•UITextField
•UITextField
•UIButton
ViewModel
Model
•tapFunction()
•doSomething()
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()
}
}
•UITextField
•UITextField
•UIButton
ViewModel
Model
•tapFunction()
•doSomething()
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
}
}
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!
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
}
}
class LoginViewController {
@IBOutlet var confirmButton: UIButton!
var loginViewModel = LoginViewModel()
override func viewDidLoad(){
super.viewDidLoad()
loginViewModel.isValidCallback = { [weak self] (isValid) in
self?.confirmButton.isEnabled = isValid
}
}
}
Unidirectional Data Flow with MVVM is hard
UIKit
ViewModel
Model
How can RxSwift help with this?
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
}
}
}
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)
}
}
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 😔
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
class ViewController {
let myCustomView : MyCustomView
override func viewDidLoad(){
super.viewDidLoad()
viewModel.dataStream
.bindTo(myCustomView.sink)
.addTo(disposeBag)
}
}
Custom Binding Sinks
Looking Forward
pod 'RxSwift'
pod 'RxCocoa'
#the good stuff
pod 'RxDataSource'