Alexander Murphy
https://www.slideshare.net/scott.gardner/reactive-programming-with-rxswift
1. onNext()
2. OnError()
3. onComplete()
4. Disposal
let arr1: [Int] = [1, 2, 3, 4, 5]
let arr2: [Int] = [6, 7]
let arr3: [Int] = [8, 9, 10]
// Map -> [2, 4, 6, 8, 10]
let mapped = arr1.map{ $0 * 2 }
// Flat Map -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let combo: [[Int]] = [arr1, arr2, arr3]
let flatMapped = combo.flatMap{ $0 }
// Filter -> [1, 3, 5]
let filtered = arr1.filter{ $0 % 2 != 0 }
// Reduce -> 15
let reduced = arr1.reduce(0, { $0 + $1 })
// Filter and Reduce -> 9
let oddsSum = arr1.filter{ $0 % 2 != 0 }.reduce(0, { $0 + $1 })
Transformations with Immutable Arrays
import UIKit
import RxSwift
import RxCocoa
class EmailInputViewController: UIViewController {
let textField = UITextField()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// add subview and constraints
// configure observable
_ = self.textField.rx
.text
.subscribe(onNext: {
print("text here: \($0)")
}).addDisposableTo(self.disposeBag)
}
}
Observable UITextField
Observable UITextField Use Case:
Validating an Email address
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController { }
Class Properties
//Observable garbage collection
let disposeBag = DisposeBag()
// Email input text field
let textField: UITextField = {
let field = UITextField()
field.backgroundColor = UIColor.black
field.textColor = UIColor.white
field.layer.borderColor = UIColor.red.cgColor
field.layer.cornerRadius = 5
field.layer.borderWidth = 3
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
// View constraints
lazy var textFieldConstraints: [NSLayoutConstraint] = [
NSLayoutConstraint(item: self.textField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 50),
NSLayoutConstraint(item: self.textField, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.textField, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.textField, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: 50),
NSLayoutConstraint(item: self.textField, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1, constant: -50)
]
Observable Class Properties
// Email Validation
lazy var emailValidationObservable: Observable<Bool> = self.textField
.rx
.text
.debounce(0.3, scheduler: MainScheduler.instance)
.map{ text -> Bool in
guard let text = text else { return false }
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
return NSPredicate(format:"SELF MATCHES %@", emailRegEx).evaluate(with: text)
}
// Email Value
lazy var emailContentObserver: Observable<[String:String]> = self.textField
.rx
.text
.orEmpty
.debounce(0.3, scheduler: MainScheduler.instance)
.flatMap{ text -> Observable<[String:String]> in
return text.isEmpty ? .just([String: String]()) : .just(["email": text])
}
Graphic sourced from http://rxmarbles.com/
View Configuration and Observers
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.textField)
NSLayoutConstraint.activate(self.textFieldConstraints)
configureObservers()
}
func configureObservers() {
_ = self.emailValidationObservable.subscribe(onNext: { hasValidated in
self.textField.layer.borderColor = hasValidated ? UIColor.green.cgColor : UIColor.red.cgColor
print(hasValidated ? "Email is valid." : "Email is invalid")
})
.addDisposableTo(self.disposeBag)
_ = self.emailContentObserver.subscribe(onNext: { email in
print("email input has changed: \(email)")
})
.addDisposableTo(self.disposeBag)
}
Form Field Cell SubClass
class InputCell: UITableViewCell {
let title: String
let validator: NSPredicate
let disposeBag = DisposeBag()
lazy var textField: UITextField
lazy var textFieldConstraints: [NSLayoutConstraint]
lazy var validationObservable: Observable<Bool>
lazy var contentObservable: Observable<[String:String]>
init(title: String, validator: NSPredicate) {
self.title = title
self.validator = validator
super.init(style: .default, reuseIdentifier: nil)
self.contentView.addSubview(self.textField)
NSLayoutConstraint.activate(textFieldConstraints)
self.contentView.backgroundColor = UIColor.black
}
}
Form Validators and ValidUser struct
struct Validators {
// Valid Email
static let email = NSPredicate(format: "SELF MATCHES %@", "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}")
// Numeric Input
static let phone = NSPredicate(format: "SELF MATCHES %@", "^[0-9]+$")
// Longer than 4 chars, less than 8
static let password = NSPredicate(format: "SELF MATCHES %@", "^.{4,8}$")
}
struct ValidUser {
let email: String
let phone: String
let password: String
}
UITextField Observables
// FieldCell class properties
lazy var validationObservable: Observable<Bool> = self.textField
.rx
.text
.debounce(0.3, scheduler: MainScheduler.instance)
.map{ text -> Bool in
guard let text = text else { return false }
let hasValidated = self.validator.evaluate(with: text)
self.textField.layer.borderColor = hasValidated ? UIColor.green.cgColor : UIColor.red.cgColor
return hasValidated
}
lazy var contentObservable: Observable<[String:String]> = self.textField
.rx
.text
.orEmpty
.debounce(0.3, scheduler: MainScheduler.instance)
.flatMap{ text -> Observable<[String:String]> in
return text.isEmpty ? .just([String: String]()) : .just([self.title: text])
}
View Controller containing UITableView
// FieldCell class properties
class ViewController: UIViewController {
let disposeBag = DisposeBag()
lazy var tableViewConstraints: [NSLayoutConstraint]
var formHasValidated: Bool = false
let cells: [UITableViewCell] = [
InputCell(title: "Email", validator: Validators.email),
InputCell(title: "Phone Number", validator: Validators.phone),
InputCell(title: "Password", validator: Validators.password)
]
lazy var table: UITableView = {
let table = UITableView()
table.delegate = self
table.dataSource = self
table.translatesAutoresizingMaskIntoConstraints = false
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.table)
NSLayoutConstraint.activate(self.tableViewConstraints)
configureValidationObserver()
configureContentObserver()
}
}
View Controller UITableView Delegates
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return self.cells[indexPath.row]
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.cells.count
}
}
Validation Observer for all fields
func configureValidationObserver(){
var validationObservers = [Observable<Bool>]()
for cell in self.cells {
if let cell = cell as? InputCell {
validationObservers.append(cell.validationObservable)
}
}
_ = Observable.combineLatest(validationObservers) { registrationObservers -> Bool in
return registrationObservers.reduce(true, { $0 && $1 })
}
.subscribe(onNext:{ validationStatus in
self.formHasValidated = validationStatus
}).addDisposableTo(disposeBag)
}
Content Observer for all fields
func configureContentObserver() {
var contentObservables = [Observable<[String:String]>]()
for cell in self.cells {
if let cell = cell as? InputCell {
contentObservables.append(cell.contentObservable)
}
}
_ = Observable.combineLatest(contentObservables)
{ contentObservables -> [String:String] in
return contentObservables
.flatMap{ $0 }
.reduce( [String:String]() ) { (dict, tuple) -> [String:String] in
var mutableDict = dict
mutableDict.updateValue(tuple.1, forKey: tuple.0)
return mutableDict
}
}
.subscribe(onNext:{ registrationFieldsDict in
print("Registration Fields: \(registrationFieldsDict)")
let user = ValidUser(
email: registrationFieldsDict["Email"]! as String,
phone: registrationFieldsDict["Phone"]! as String,
password: registrationFieldsDict["Password"]! as String
)
// Make API Call with valid user
}).addDisposableTo(disposeBag)
}
In other words, a Variable data point we can mutate programmatically.
Observable Variable
let disposeBag = DisposeBag()
let number = Variable(0)
number.asObservable().subscribe{ print("new value here: \($0)") }
for i in 0...4 {
number = i
}
// Prints "Next(0), Next(1), Next(2), Next(3)"
// Create a NotificationCenter Observer
public func addObserverForName( name: String?,
object obj: AnyObject?,
queue: NSOperationQueue?,
usingBlock block: (NSNotification) -> Void) -> NSObjectProtocol
// Create a NotificationCenter Observer with RxSwift
NotificationCenter.default
.rx.notification(NSNotification.Name.UITextViewTextDidBeginEditing, object: myTextView)
.map { /*do something with data*/ }
let data = Observable<[String]>.just(["first element", "second element", "third element"])
data.bindTo(tableView.rx.items(cellIdentifier: "Cell")) { index, model, cell in
cell.textLabel?.text = model
}
.disposed(by: disposeBag)
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Int>>()
Observable.just([SectionModel(model: "title", items: [1, 2, 3])])
.bindTo(tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
Model
View
Controller
Model
View
Controller
Persistence
var questions: [String]?