.(
) . ..)
!
Disk Defragment
Code Defragment
→ →
Advantages
EASY to UNDERSTAND
READ
FOLLOW
FIND
MAINTENANCE
Goal
→
NO FREE LUNCH
CASE STUDY
CASE #1. Data + Logic + UI
class ViewController: UIViewController {
@IBOutlet var number1: UILabel!
@IBOutlet var number2: UILabel!
@IBOutlet var result: UILabel!
struct CalcData {
let num1: Int
let num2: Int
}
private func textToInt(_ text: String?) -> Int {
guard let text = text else { return 0 }
return Int(text) ?? 0
}
@IBAction func calcResult(_ sender: Any) {
let n1 = textToInt(number1.text)
let n2 = textToInt(number2.text)
let data = CalcData(num1: n1, num2: n2)
let res = calcPlus(data)
result.text = intToText(res)
}
private func intToText(_ num: Int) -> String {
return "(num)"
}
private func calcPlus(_ data: CalcData) -> Int {
return data.num1 + data.num2
}
}
❓
CASE #1. Data + Logic + UI
class ViewController: UIViewController {
// MARK: - DATA
struct CalcData {
let num1: Int
let num2: Int
}
// MARK: - LOGIC
private func textToInt(_ text: String?) -> Int {
guard let text = text else { return 0 }
return Int(text) ?? 0
}
private func intToText(_ num: Int) -> String {
return "(num)"
}
private func calcPlus(_ data: CalcData) -> Int {
return data.num1 + data.num2
}
// MARK: - UI
@IBOutlet var number1: UILabel!
@IBOutlet var number2: UILabel!
@IBOutlet var result: UILabel!
@IBAction func calcResult(_ sender: Any) {
let n1 = textToInt(number1.text)
let n2 = textToInt(number2.text)
let data = CalcData(num1: n1, num2: n2)
let res = calcPlus(data)
result.text = intToText(res)
}
}
❗
CASE #2. Data Setter ❓
class ViewController: UIViewController {
@IBOutlet var textLabel: UILabel!
var count: Int = 0
@IBAction func addOne() {
count += 1
textLabel.text = "(count)"
}
@IBAction func subOne() {
count -= 1
textLabel.text = "(count)" // duplicated
}
}
CASE #2. Data Setter ❗
class ViewController: UIViewController {
@IBOutlet var textLabel: UILabel!
var count: Int = 0 {
didSet {
textLabel.text = "(count)"
}
}
@IBAction func addOne() {
count += 1
}
@IBAction func subOne() {
count -= 1
}
}
CASE #3. Overrides
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar()
}
func setupNavigationBar() {
self.navigationController?.title = "App Title"
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: animated)
//Other Codes
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: animated)
//Other Codes
}
}
❓
class BaseViewController: UIViewController {
var viewWillAppearActions: [(Bool) -> ()] = []
var viewWillDisappearActions: [(Bool) -> ()] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
viewWillAppearActions.forEach({ $0(animated) })
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
viewWillDisappearActions.forEach({ $0(animated) })
}
}
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar()
}
func setupNavigationBar() {
self.navigationController?.title = "App Title"
viewWillAppearActions.append({ [weak self] anim in
self?.navigationController?.setNavigationBarHidden(false, animated: anim)
})
viewWillDisappearActions.append({ [weak self] anim in
self?.navigationController?.setNavigationBarHidden(true, animated: anim)
})
}
}
❗CASE #3. Overrides
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupKeyboardEvent()
}
func setupKeyboardEvent() {
NotificationCenter.default.addObserver(self,
selector: #selector(onKeyboardWillShow),
name: .UIKeyboardWillShow,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(onKeyboardWillHide),
name: .UIKeyboardWillHide,
object: nil)
}
@objc func onKeyboardWillShow(notification: Notification) {
print("Keyboard will show")
}
@objc func onKeyboardWillHide(notification: Notification) {
print("Keyboard will hide")
}
}
CASE #4. Selector ❓
class KeyboardEventWrapper {
var onKeyboardWillShowCallBack: (Notification) -> () = { _ in }
init() {
NotificationCenter.default.addObserver(self,
selector: #selector(onKeyboardWillShow),
name: .UIKeyboardWillShow,
object: nil)
}
@objc func onKeyboardWillShow(notification: Notification) {
onKeyboardWillShowCallBack(notification)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
class ViewController: UIViewController {
var keyboardEventWrapper: KeyboardEventWrapper!
override func viewDidLoad() {
super.viewDidLoad()
setupKeyboardEvent()
}
func setupKeyboardEvent() {
keyboardEventWrapper = KeyboardEventWrapper()
keyboardEventWrapper.onKeyboardWillShowCallBack = { _ in
print("Keyboard will show")
}
}
}
❗CASE #4. Selector
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate
{
@IBAction func onLoadImage() {
let picker = UIImagePickerController()
picker.delegate = self
present(picker, animated: true, completion: nil)
}
// MARK: - UIImagePickerControllerDelegate
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String: Any]) {
let originalInfo = info[UIImagePickerControllerOriginalImage]
guard let originalImage = originalInfo as? UIImage else { return }
// ...
picker.dismiss(animated: true, completion: nil)
}
}
CASE #5. Delegate ❓
class ImagePickerWrapper: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var onImagePicked: (UIImage?) -> () = { _ in }
func showImagePicker(on vc: UIViewController) {
let picker = UIImagePickerController()
picker.delegate = self
vc.present(picker, animated: true, completion: nil)
}
// MARK: - UIImagePickerControllerDelegate
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String: Any]) {
let originalInfo = info[UIImagePickerControllerOriginalImage]
guard let originalImage = originalInfo as? UIImage else { return }
onImagePicked(originalImage)
picker.dismiss(animated: true, completion: nil)
}
}
class ViewController: UIViewController {
var imagePicker: ImagePickerWrapper!
@IBAction func onLoadImage() {
imagePicker = ImagePickerWrapper()
imagePicker.showImagePicker(on: self)
imagePicker.onImagePicked = { image in
guard let image = image else { return }
// ...
}
}
}
❗CASE #5. Delegate
ELEGANT WAY
feat. RxSwift
CASE #1. Data + Logic + UI
struct CalcData {
let num1: Int
let num2: Int
}
func textToInt(_ text: String?) -> Int {
guard let text = text else { return 0 }
return Int(text) ?? 0
}
func intToText(_ num: Int) -> String {
return "(num)"
}
func calcPlus(_ data: CalcData) -> Int {
return data.num1 + data.num2
}
class ViewController: UIViewController {
@IBOutlet var number1: UILabel!
@IBOutlet var number2: UILabel!
@IBOutlet var result: UILabel!
@IBAction func calcResult(_ sender: Any) {
let n1 = textToInt(number1.text)
let n2 = textToInt(number2.text)
let data = CalcData(num1: n1, num2: n2)
let res = calcPlus(data)
result.text = intToText(res)
}
}
Model.swift
Context.swift
ViewController.swift
❗❗
CASE #2. Data Setter
import RxSwift
import RxCocoa
// Common Functions
let intToText = { (i: Int) in "(i)" }
///
class ViewController: UIViewController {
@IBOutlet var textLabel: UILabel!
let disposeBag = DisposeBag()
let count = BehaviorRelay<Int>(value: 0)
override func viewDidLoad() {
super.viewDidLoad()
count.map(intToText)
.bind(to: textLabel.rx.text)
.disposed(by: disposeBag)
}
@IBAction func addOne() {
count.accept(count.value + 1)
}
@IBAction func subOne() {
count.accept(count.value - 1)
}
}
❗❗
CASE #2. Data Setter
import RxSwift
import RxCocoa
// Common Functions
let intToText = { (i: Int) in "(i)" }
///
class ViewController: UIViewController {
@IBOutlet var textLabel: UILabel!
@IBOutlet var textLabelx10: UILabel!
let disposeBag = DisposeBag()
let count = BehaviorRelay<Int>(value: 0)
override func viewDidLoad() {
super.viewDidLoad()
count.map(intToText)
.bind(to: textLabel.rx.text)
.disposed(by: disposeBag)
count.map({ $0 * 10 })
.map(intToText)
.bind(to: textLabelx10.rx.text)
.disposed(by: disposeBag)
}
@IBAction func addOne() {
count.accept(count.value + 1)
}
@IBAction func subOne() {
count.accept(count.value - 1)
}
}
❗❗
import RxSwift
import RxCocoa
import RxViewController // <==
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar()
}
func setupNavigationBar() {
self.navigationController?.title = "App Title"
self.rx.viewWillAppear.subscribe(onNext: { [weak self] anim in
self?.navigationController?.setNavigationBarHidden(false, animated: anim)
}).disposed(by: disposeBag)
self.rx.viewWillDisappear.subscribe(onNext: { [weak self] anim in
self?.navigationController?.setNavigationBarHidden(true, animated: anim)
}).disposed(by: disposeBag)
}
}
CASE #3. Overrides ❗❗
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
setupKeyboardEvent()
}
func setupKeyboardEvent() {
NotificationCenter.default.rx.notification(.UIKeyboardWillShow)
.subscribe(onNext: { notifiaction in
print("Keyboard will show")
})
.disposed(by: disposeBag)
NotificationCenter.default.rx.notification(.UIKeyboardWillHide)
.subscribe(onNext: { notifiaction in
print("Keyboard will hide")
})
.disposed(by: disposeBag)
}
}
CASE #4. Selector ❗❗
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
@IBOutlet weak var imageView: UIImageView!
@IBAction func onLoadImage() {
let picker = UIImagePickerController()
present(picker, animated: true, completion: nil)
picker.rx.didFinishPickingMediaWithInfo
.map({ info -> UIImage? in
info[UIImagePickerControllerOriginalImage] as? UIImage
})
.bind(to: imageView.rx.image)
.disposed(by: disposeBag)
}
}
CASE #5. Delegate ❗❗
// MARK:- UIImagePickerController.rx
// picker.rx.didFinishPickingMediaWithInfo
// ~~~~~~ ~~
// Base Reactive
extension Reactive where Base: UIImagePickerController {
public var didFinishPickingMediaWithInfo: Observable<[String : Any]> {
return RxImagePickerProxy.proxy(for: base)
.didFinishPickingMediaWithInfoSubject
.asObservable()
.do(onCompleted: {
self.base.dismiss(animated: true, completion: nil)
})
}
public var didCancel: Observable<Void> {
return RxImagePickerProxy.proxy(for: base)
.didCancelSubject
.asObservable()
.do(onCompleted: {
self.base.dismiss(animated: true, completion: nil)
})
}
}
CASE #5. Delegate ❗❗
import UIKit
import RxSwift
import RxCocoa
public typealias ImagePickerDelegate = UIImagePickerControllerDelegate & UINavigationControllerDelegate
extension UIImagePickerController: HasDelegate {
public typealias Delegate = ImagePickerDelegate
}
class RxImagePickerProxy: DelegateProxy<UIImagePickerController, ImagePickerDelegate>,
DelegateProxyType, UIImagePickerControllerDelegate, UINavigationControllerDelegate
{
public init(imagePicker: UIImagePickerController) {
super.init(parentObject: imagePicker, delegateProxy: RxImagePickerProxy.self)
}
//MARK:- DelegateProxyType
public static func registerKnownImplementations() {
self.register { RxImagePickerProxy(imagePicker: $0) }
}
static func currentDelegate(for object: UIImagePickerController) -> ImagePickerDelegate? {
return object.delegate
}
static func setCurrentDelegate(_ delegate: ImagePickerDelegate?, to object: UIImagePickerController)
{
object.delegate = delegate
}
//MARK:- Proxy Subject
//MARK:- UIImagePickerControllerDelegate
//MARK:- Completed
}
CASE #5. Delegate ❗❗
class RxImagePickerProxy: DelegateProxy<UIImagePickerController, ImagePickerDelegate>,
DelegateProxyType, UIImagePickerControllerDelegate, UINavigationControllerDelegate
{
//MARK:- DelegateProxyType
//MARK:- Proxy Subject
internal lazy var didFinishPickingMediaWithInfoSubject = PublishSubject<[String : Any]>()
internal lazy var didCancelSubject = PublishSubject<Void>()
//MARK:- UIImagePickerControllerDelegate
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String: Any]) {
didFinishPickingMediaWithInfoSubject.onNext(info)
didFinishPickingMediaWithInfoSubject.onCompleted()
didCancelSubject.onCompleted()
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
didCancelSubject.onNext(())
didCancelSubject.onCompleted()
didFinishPickingMediaWithInfoSubject.onCompleted()
}
//MARK:- Completed
deinit {
self.didFinishPickingMediaWithInfoSubject.onCompleted()
self.didCancelSubject.onCompleted()
}
}
CASE #5. Delegate ❗❗
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
@IBOutlet weak var imageView: UIImageView!
@IBAction func onLoadImage() {
let picker = UIImagePickerController()
present(picker, animated: true, completion: nil)
picker.rx.didFinishPickingMediaWithInfo
.map({ info -> UIImage? in
info[UIImagePickerControllerOriginalImage] as? UIImage
})
.bind(to: imageView.rx.image)
.disposed(by: disposeBag)
}
}
CASE #5. Delegate ❗❗
ALL TOGETHER
SUMMARY
UI
Model.swift
struct CalcData {
let n1: Int?
let n2: Int?
let result: Int?
}
extension CalcData {
static func empty() -> CalcData {
return CalcData(n1: nil, n2: nil, result: nil)
}
}
Context.swift (Business Logic)
let i2s = { (i: Int?) in "(i ?? 0)" }
let s2i = { (s: String?) in Int(s ?? "0") }
class Context {
let model = BehaviorRelay<CalcData>(value: CalcData.empty())
func doCalc(data: CalcData) -> CalcData {
guard let n1 = data.n1 else { return data }
guard let n2 = data.n2 else { return data }
let result = n1 + n2
return CalcData(n1: n1, n2: n2, result: result)
}
}
ViewController.swift
class ViewController: UIViewController {
@IBOutlet weak var n1Field: UITextField! // Input
@IBOutlet weak var n2Field: UITextField! // Input
@IBOutlet weak var resultLabel: UILabel! // Output
@IBOutlet weak var calcButton: UIButton! // Event
let context = Context()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
bindUI()
bindEvent()
}
func bindUI() {
// ...
}
func bindEvent() {
// …
}
}
ViewController.swift
class ViewController: UIViewController {
func bindUI() {
//n1 input
n1Field.rx.text.map(s2i)
.withLatestFrom(context.model) { (n1, dat) in
CalcData(n1: n1, n2: dat.n2, result: dat.result)
}
.bind(to: context.model)
.disposed(by: disposeBag)
//n2 input
n2Field.rx.text.map(s2i)
.withLatestFrom(context.model) { (n2, dat) in
CalcData(n1: dat.n1, n2: n2, result: dat.result)
}
.bind(to: context.model)
.disposed(by: disposeBag)
//result output
viewModel.model.map({ $0.result })
.map(i2s)
.bind(to: resultLabel.rx.text)
.disposed(by: disposeBag)
}
func bindEvent() {
//event
calcButton.rx.tap
.withLatestFrom(context.model)
.map(context.doCalc)
.bind(to: context.model)
.disposed(by: disposeBag)
}
}
Summary
class ViewController: UIViewController {
@IBOutlet weak var n1Field: UITextField! // Input
@IBOutlet weak var n2Field: UITextField! // Input
@IBOutlet weak var resultLabel: UILabel! // Output
@IBOutlet weak var calcButton: UIButton! // Event
let context = Context()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
bindUI()
bindEvent()
}
func bindUI() {
//n1 input
n1Field.rx.text.map(s2i)
.withLatestFrom(context.model) { (n1, dat) in
CalcData(n1: n1, n2: dat.n2, result: dat.result)
}
.bind(to: context.model)
.disposed(by: disposeBag)
//n2 input
n2Field.rx.text.map(s2i)
.withLatestFrom(context.model) { (n2, dat) in
CalcData(n1: dat.n1, n2: n2, result: dat.result)
}
.bind(to: context.model)
.disposed(by: disposeBag)
//result output
viewModel.model.map({ $0.result })
.map(i2s)
.bind(to: resultLabel.rx.text)
.disposed(by: disposeBag)
}
func bindEvent() {
//event
calcButton.rx.tap
.withLatestFrom(context.model)
.map(context.doCalc)
.bind(to: context.model)
.disposed(by: disposeBag)
}
}
struct CalcData {
let n1: Int?
let n2: Int?
let result: Int?
}
extension CalcData {
static func empty() -> CalcData {
return CalcData(n1: nil, n2: nil, result: nil)
}
}
View
ViewController
Context
Model
let i2s = { (i: Int?) in "(i ?? 0)" }
let s2i = { (s: String?) in Int(s ?? "0") }
class Context {
let model = BehaviorRelay<CalcData>(value: CalcData.empty())
func doCalc(data: CalcData) -> CalcData {
guard let n1 = data.n1 else { return data }
guard let n2 = data.n2 else { return data }
let result = n1 + n2
return CalcData(n1: n1, n2: n2, result: result)
}
}
struct CalcData {
let n1: Int?
let n2: Int?
let result: Int?
}
extension CalcData {
static func empty() -> CalcData {
return CalcData(n1: nil, n2: nil, result: nil)
}
}
View
ViewController
Context
Model
ViewModel
Summary
class ViewController: UIViewController {
@IBOutlet weak var n1Field: UITextField! // Input
@IBOutlet weak var n2Field: UITextField! // Input
@IBOutlet weak var resultLabel: UILabel! // Output
@IBOutlet weak var calcButton: UIButton! // Event
let viewModel = ViewModel()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
bindUI()
bindEvent()
}
func bindUI() {
//n1 input
n1Field.rx.text.map(s2i)
.withLatestFrom(viewModel.model) { (n1, dat) in
CalcData(n1: n1, n2: dat.n2, result: dat.result)
}
.bind(to: viewModel.model)
.disposed(by: disposeBag)
//n2 input
n2Field.rx.text.map(s2i)
.withLatestFrom(viewModel.model) { (n2, dat) in
CalcData(n1: dat.n1, n2: n2, result: dat.result)
}
.bind(to: viewModel.model)
.disposed(by: disposeBag)
//result output
viewModel.model.map({ $0.result })
.map(i2s)
.bind(to: resultLabel.rx.text)
.disposed(by: disposeBag)
}
func bindEvent() {
//event
calcButton.rx.tap
.withLatestFrom(viewModel.model)
.map(viewModel.doCalc)
.bind(to: viewModel.model)
.disposed(by: disposeBag)
}
}
let i2s = { (i: Int?) in "(i ?? 0)" }
let s2i = { (s: String?) in Int(s ?? "0") }
class ViewModel {
let model = BehaviorRelay<CalcData>(value: CalcData.empty())
func doCalc(data: CalcData) -> CalcData {
guard let n1 = data.n1 else { return data }
guard let n2 = data.n2 else { return data }
let result = n1 + n2
return CalcData(n1: n1, n2: n2, result: result)
}
}
20180721 code defragment

20180721 code defragment

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
    CASE #1. Data+ Logic + UI class ViewController: UIViewController { @IBOutlet var number1: UILabel! @IBOutlet var number2: UILabel! @IBOutlet var result: UILabel! struct CalcData { let num1: Int let num2: Int } private func textToInt(_ text: String?) -> Int { guard let text = text else { return 0 } return Int(text) ?? 0 } @IBAction func calcResult(_ sender: Any) { let n1 = textToInt(number1.text) let n2 = textToInt(number2.text) let data = CalcData(num1: n1, num2: n2) let res = calcPlus(data) result.text = intToText(res) } private func intToText(_ num: Int) -> String { return "(num)" } private func calcPlus(_ data: CalcData) -> Int { return data.num1 + data.num2 } } ❓
  • 8.
    CASE #1. Data+ Logic + UI class ViewController: UIViewController { // MARK: - DATA struct CalcData { let num1: Int let num2: Int } // MARK: - LOGIC private func textToInt(_ text: String?) -> Int { guard let text = text else { return 0 } return Int(text) ?? 0 } private func intToText(_ num: Int) -> String { return "(num)" } private func calcPlus(_ data: CalcData) -> Int { return data.num1 + data.num2 } // MARK: - UI @IBOutlet var number1: UILabel! @IBOutlet var number2: UILabel! @IBOutlet var result: UILabel! @IBAction func calcResult(_ sender: Any) { let n1 = textToInt(number1.text) let n2 = textToInt(number2.text) let data = CalcData(num1: n1, num2: n2) let res = calcPlus(data) result.text = intToText(res) } } ❗
  • 9.
    CASE #2. DataSetter ❓ class ViewController: UIViewController { @IBOutlet var textLabel: UILabel! var count: Int = 0 @IBAction func addOne() { count += 1 textLabel.text = "(count)" } @IBAction func subOne() { count -= 1 textLabel.text = "(count)" // duplicated } }
  • 10.
    CASE #2. DataSetter ❗ class ViewController: UIViewController { @IBOutlet var textLabel: UILabel! var count: Int = 0 { didSet { textLabel.text = "(count)" } } @IBAction func addOne() { count += 1 } @IBAction func subOne() { count -= 1 } }
  • 11.
    CASE #3. Overrides classViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setupNavigationBar() } func setupNavigationBar() { self.navigationController?.title = "App Title" } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.navigationController?.setNavigationBarHidden(false, animated: animated) //Other Codes } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.navigationController?.setNavigationBarHidden(true, animated: animated) //Other Codes } } ❓
  • 12.
    class BaseViewController: UIViewController{ var viewWillAppearActions: [(Bool) -> ()] = [] var viewWillDisappearActions: [(Bool) -> ()] = [] override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) viewWillAppearActions.forEach({ $0(animated) }) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) viewWillDisappearActions.forEach({ $0(animated) }) } } class ViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() setupNavigationBar() } func setupNavigationBar() { self.navigationController?.title = "App Title" viewWillAppearActions.append({ [weak self] anim in self?.navigationController?.setNavigationBarHidden(false, animated: anim) }) viewWillDisappearActions.append({ [weak self] anim in self?.navigationController?.setNavigationBarHidden(true, animated: anim) }) } } ❗CASE #3. Overrides
  • 13.
    class ViewController: UIViewController{ override func viewDidLoad() { super.viewDidLoad() setupKeyboardEvent() } func setupKeyboardEvent() { NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardWillShow), name: .UIKeyboardWillShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardWillHide), name: .UIKeyboardWillHide, object: nil) } @objc func onKeyboardWillShow(notification: Notification) { print("Keyboard will show") } @objc func onKeyboardWillHide(notification: Notification) { print("Keyboard will hide") } } CASE #4. Selector ❓
  • 14.
    class KeyboardEventWrapper { varonKeyboardWillShowCallBack: (Notification) -> () = { _ in } init() { NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardWillShow), name: .UIKeyboardWillShow, object: nil) } @objc func onKeyboardWillShow(notification: Notification) { onKeyboardWillShowCallBack(notification) } deinit { NotificationCenter.default.removeObserver(self) } } class ViewController: UIViewController { var keyboardEventWrapper: KeyboardEventWrapper! override func viewDidLoad() { super.viewDidLoad() setupKeyboardEvent() } func setupKeyboardEvent() { keyboardEventWrapper = KeyboardEventWrapper() keyboardEventWrapper.onKeyboardWillShowCallBack = { _ in print("Keyboard will show") } } } ❗CASE #4. Selector
  • 15.
    class ViewController: UIViewController,UIImagePickerControllerDelegate, UINavigationControllerDelegate { @IBAction func onLoadImage() { let picker = UIImagePickerController() picker.delegate = self present(picker, animated: true, completion: nil) } // MARK: - UIImagePickerControllerDelegate func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) { let originalInfo = info[UIImagePickerControllerOriginalImage] guard let originalImage = originalInfo as? UIImage else { return } // ... picker.dismiss(animated: true, completion: nil) } } CASE #5. Delegate ❓
  • 16.
    class ImagePickerWrapper: NSObject,UIImagePickerControllerDelegate, UINavigationControllerDelegate { var onImagePicked: (UIImage?) -> () = { _ in } func showImagePicker(on vc: UIViewController) { let picker = UIImagePickerController() picker.delegate = self vc.present(picker, animated: true, completion: nil) } // MARK: - UIImagePickerControllerDelegate func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) { let originalInfo = info[UIImagePickerControllerOriginalImage] guard let originalImage = originalInfo as? UIImage else { return } onImagePicked(originalImage) picker.dismiss(animated: true, completion: nil) } } class ViewController: UIViewController { var imagePicker: ImagePickerWrapper! @IBAction func onLoadImage() { imagePicker = ImagePickerWrapper() imagePicker.showImagePicker(on: self) imagePicker.onImagePicked = { image in guard let image = image else { return } // ... } } } ❗CASE #5. Delegate
  • 17.
  • 18.
    CASE #1. Data+ Logic + UI struct CalcData { let num1: Int let num2: Int } func textToInt(_ text: String?) -> Int { guard let text = text else { return 0 } return Int(text) ?? 0 } func intToText(_ num: Int) -> String { return "(num)" } func calcPlus(_ data: CalcData) -> Int { return data.num1 + data.num2 } class ViewController: UIViewController { @IBOutlet var number1: UILabel! @IBOutlet var number2: UILabel! @IBOutlet var result: UILabel! @IBAction func calcResult(_ sender: Any) { let n1 = textToInt(number1.text) let n2 = textToInt(number2.text) let data = CalcData(num1: n1, num2: n2) let res = calcPlus(data) result.text = intToText(res) } } Model.swift Context.swift ViewController.swift ❗❗
  • 19.
    CASE #2. DataSetter import RxSwift import RxCocoa // Common Functions let intToText = { (i: Int) in "(i)" } /// class ViewController: UIViewController { @IBOutlet var textLabel: UILabel! let disposeBag = DisposeBag() let count = BehaviorRelay<Int>(value: 0) override func viewDidLoad() { super.viewDidLoad() count.map(intToText) .bind(to: textLabel.rx.text) .disposed(by: disposeBag) } @IBAction func addOne() { count.accept(count.value + 1) } @IBAction func subOne() { count.accept(count.value - 1) } } ❗❗
  • 20.
    CASE #2. DataSetter import RxSwift import RxCocoa // Common Functions let intToText = { (i: Int) in "(i)" } /// class ViewController: UIViewController { @IBOutlet var textLabel: UILabel! @IBOutlet var textLabelx10: UILabel! let disposeBag = DisposeBag() let count = BehaviorRelay<Int>(value: 0) override func viewDidLoad() { super.viewDidLoad() count.map(intToText) .bind(to: textLabel.rx.text) .disposed(by: disposeBag) count.map({ $0 * 10 }) .map(intToText) .bind(to: textLabelx10.rx.text) .disposed(by: disposeBag) } @IBAction func addOne() { count.accept(count.value + 1) } @IBAction func subOne() { count.accept(count.value - 1) } } ❗❗
  • 21.
    import RxSwift import RxCocoa importRxViewController // <== class ViewController: UIViewController { let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() setupNavigationBar() } func setupNavigationBar() { self.navigationController?.title = "App Title" self.rx.viewWillAppear.subscribe(onNext: { [weak self] anim in self?.navigationController?.setNavigationBarHidden(false, animated: anim) }).disposed(by: disposeBag) self.rx.viewWillDisappear.subscribe(onNext: { [weak self] anim in self?.navigationController?.setNavigationBarHidden(true, animated: anim) }).disposed(by: disposeBag) } } CASE #3. Overrides ❗❗
  • 22.
    import UIKit import RxSwift importRxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() setupKeyboardEvent() } func setupKeyboardEvent() { NotificationCenter.default.rx.notification(.UIKeyboardWillShow) .subscribe(onNext: { notifiaction in print("Keyboard will show") }) .disposed(by: disposeBag) NotificationCenter.default.rx.notification(.UIKeyboardWillHide) .subscribe(onNext: { notifiaction in print("Keyboard will hide") }) .disposed(by: disposeBag) } } CASE #4. Selector ❗❗
  • 23.
    import UIKit import RxSwift importRxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() @IBOutlet weak var imageView: UIImageView! @IBAction func onLoadImage() { let picker = UIImagePickerController() present(picker, animated: true, completion: nil) picker.rx.didFinishPickingMediaWithInfo .map({ info -> UIImage? in info[UIImagePickerControllerOriginalImage] as? UIImage }) .bind(to: imageView.rx.image) .disposed(by: disposeBag) } } CASE #5. Delegate ❗❗
  • 24.
    // MARK:- UIImagePickerController.rx //picker.rx.didFinishPickingMediaWithInfo // ~~~~~~ ~~ // Base Reactive extension Reactive where Base: UIImagePickerController { public var didFinishPickingMediaWithInfo: Observable<[String : Any]> { return RxImagePickerProxy.proxy(for: base) .didFinishPickingMediaWithInfoSubject .asObservable() .do(onCompleted: { self.base.dismiss(animated: true, completion: nil) }) } public var didCancel: Observable<Void> { return RxImagePickerProxy.proxy(for: base) .didCancelSubject .asObservable() .do(onCompleted: { self.base.dismiss(animated: true, completion: nil) }) } } CASE #5. Delegate ❗❗
  • 25.
    import UIKit import RxSwift importRxCocoa public typealias ImagePickerDelegate = UIImagePickerControllerDelegate & UINavigationControllerDelegate extension UIImagePickerController: HasDelegate { public typealias Delegate = ImagePickerDelegate } class RxImagePickerProxy: DelegateProxy<UIImagePickerController, ImagePickerDelegate>, DelegateProxyType, UIImagePickerControllerDelegate, UINavigationControllerDelegate { public init(imagePicker: UIImagePickerController) { super.init(parentObject: imagePicker, delegateProxy: RxImagePickerProxy.self) } //MARK:- DelegateProxyType public static func registerKnownImplementations() { self.register { RxImagePickerProxy(imagePicker: $0) } } static func currentDelegate(for object: UIImagePickerController) -> ImagePickerDelegate? { return object.delegate } static func setCurrentDelegate(_ delegate: ImagePickerDelegate?, to object: UIImagePickerController) { object.delegate = delegate } //MARK:- Proxy Subject //MARK:- UIImagePickerControllerDelegate //MARK:- Completed } CASE #5. Delegate ❗❗
  • 26.
    class RxImagePickerProxy: DelegateProxy<UIImagePickerController,ImagePickerDelegate>, DelegateProxyType, UIImagePickerControllerDelegate, UINavigationControllerDelegate { //MARK:- DelegateProxyType //MARK:- Proxy Subject internal lazy var didFinishPickingMediaWithInfoSubject = PublishSubject<[String : Any]>() internal lazy var didCancelSubject = PublishSubject<Void>() //MARK:- UIImagePickerControllerDelegate func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) { didFinishPickingMediaWithInfoSubject.onNext(info) didFinishPickingMediaWithInfoSubject.onCompleted() didCancelSubject.onCompleted() } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { didCancelSubject.onNext(()) didCancelSubject.onCompleted() didFinishPickingMediaWithInfoSubject.onCompleted() } //MARK:- Completed deinit { self.didFinishPickingMediaWithInfoSubject.onCompleted() self.didCancelSubject.onCompleted() } } CASE #5. Delegate ❗❗
  • 27.
    import UIKit import RxSwift importRxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() @IBOutlet weak var imageView: UIImageView! @IBAction func onLoadImage() { let picker = UIImagePickerController() present(picker, animated: true, completion: nil) picker.rx.didFinishPickingMediaWithInfo .map({ info -> UIImage? in info[UIImagePickerControllerOriginalImage] as? UIImage }) .bind(to: imageView.rx.image) .disposed(by: disposeBag) } } CASE #5. Delegate ❗❗
  • 28.
  • 29.
  • 30.
    Model.swift struct CalcData { letn1: Int? let n2: Int? let result: Int? } extension CalcData { static func empty() -> CalcData { return CalcData(n1: nil, n2: nil, result: nil) } }
  • 31.
    Context.swift (Business Logic) leti2s = { (i: Int?) in "(i ?? 0)" } let s2i = { (s: String?) in Int(s ?? "0") } class Context { let model = BehaviorRelay<CalcData>(value: CalcData.empty()) func doCalc(data: CalcData) -> CalcData { guard let n1 = data.n1 else { return data } guard let n2 = data.n2 else { return data } let result = n1 + n2 return CalcData(n1: n1, n2: n2, result: result) } }
  • 32.
    ViewController.swift class ViewController: UIViewController{ @IBOutlet weak var n1Field: UITextField! // Input @IBOutlet weak var n2Field: UITextField! // Input @IBOutlet weak var resultLabel: UILabel! // Output @IBOutlet weak var calcButton: UIButton! // Event let context = Context() let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() bindUI() bindEvent() } func bindUI() { // ... } func bindEvent() { // … } }
  • 33.
    ViewController.swift class ViewController: UIViewController{ func bindUI() { //n1 input n1Field.rx.text.map(s2i) .withLatestFrom(context.model) { (n1, dat) in CalcData(n1: n1, n2: dat.n2, result: dat.result) } .bind(to: context.model) .disposed(by: disposeBag) //n2 input n2Field.rx.text.map(s2i) .withLatestFrom(context.model) { (n2, dat) in CalcData(n1: dat.n1, n2: n2, result: dat.result) } .bind(to: context.model) .disposed(by: disposeBag) //result output viewModel.model.map({ $0.result }) .map(i2s) .bind(to: resultLabel.rx.text) .disposed(by: disposeBag) } func bindEvent() { //event calcButton.rx.tap .withLatestFrom(context.model) .map(context.doCalc) .bind(to: context.model) .disposed(by: disposeBag) } }
  • 34.
    Summary class ViewController: UIViewController{ @IBOutlet weak var n1Field: UITextField! // Input @IBOutlet weak var n2Field: UITextField! // Input @IBOutlet weak var resultLabel: UILabel! // Output @IBOutlet weak var calcButton: UIButton! // Event let context = Context() let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() bindUI() bindEvent() } func bindUI() { //n1 input n1Field.rx.text.map(s2i) .withLatestFrom(context.model) { (n1, dat) in CalcData(n1: n1, n2: dat.n2, result: dat.result) } .bind(to: context.model) .disposed(by: disposeBag) //n2 input n2Field.rx.text.map(s2i) .withLatestFrom(context.model) { (n2, dat) in CalcData(n1: dat.n1, n2: n2, result: dat.result) } .bind(to: context.model) .disposed(by: disposeBag) //result output viewModel.model.map({ $0.result }) .map(i2s) .bind(to: resultLabel.rx.text) .disposed(by: disposeBag) } func bindEvent() { //event calcButton.rx.tap .withLatestFrom(context.model) .map(context.doCalc) .bind(to: context.model) .disposed(by: disposeBag) } } struct CalcData { let n1: Int? let n2: Int? let result: Int? } extension CalcData { static func empty() -> CalcData { return CalcData(n1: nil, n2: nil, result: nil) } } View ViewController Context Model let i2s = { (i: Int?) in "(i ?? 0)" } let s2i = { (s: String?) in Int(s ?? "0") } class Context { let model = BehaviorRelay<CalcData>(value: CalcData.empty()) func doCalc(data: CalcData) -> CalcData { guard let n1 = data.n1 else { return data } guard let n2 = data.n2 else { return data } let result = n1 + n2 return CalcData(n1: n1, n2: n2, result: result) } }
  • 35.
    struct CalcData { letn1: Int? let n2: Int? let result: Int? } extension CalcData { static func empty() -> CalcData { return CalcData(n1: nil, n2: nil, result: nil) } } View ViewController Context Model ViewModel Summary class ViewController: UIViewController { @IBOutlet weak var n1Field: UITextField! // Input @IBOutlet weak var n2Field: UITextField! // Input @IBOutlet weak var resultLabel: UILabel! // Output @IBOutlet weak var calcButton: UIButton! // Event let viewModel = ViewModel() let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() bindUI() bindEvent() } func bindUI() { //n1 input n1Field.rx.text.map(s2i) .withLatestFrom(viewModel.model) { (n1, dat) in CalcData(n1: n1, n2: dat.n2, result: dat.result) } .bind(to: viewModel.model) .disposed(by: disposeBag) //n2 input n2Field.rx.text.map(s2i) .withLatestFrom(viewModel.model) { (n2, dat) in CalcData(n1: dat.n1, n2: n2, result: dat.result) } .bind(to: viewModel.model) .disposed(by: disposeBag) //result output viewModel.model.map({ $0.result }) .map(i2s) .bind(to: resultLabel.rx.text) .disposed(by: disposeBag) } func bindEvent() { //event calcButton.rx.tap .withLatestFrom(viewModel.model) .map(viewModel.doCalc) .bind(to: viewModel.model) .disposed(by: disposeBag) } } let i2s = { (i: Int?) in "(i ?? 0)" } let s2i = { (s: String?) in Int(s ?? "0") } class ViewModel { let model = BehaviorRelay<CalcData>(value: CalcData.empty()) func doCalc(data: CalcData) -> CalcData { guard let n1 = data.n1 else { return data } guard let n2 = data.n2 else { return data } let result = n1 + n2 return CalcData(n1: n1, n2: n2, result: result) } }