Slide 1

Slide 1 text

MVC @ IOS SU PING-CHEN (PC) LINE Fukuoka LINE Developer meetup #53 in KYOTO

Slide 2

Slide 2 text

ABOUT ME ▸ ોฑਉɹεΡɹͼʔΜͪ͐Μ ▸ Taiwanese ▸ iOS developer (6 years) ▸ LINE Fukuoka iOS developer ▸ LINE઎͍ ▸ LINE app

Slide 3

Slide 3 text

WHAT IS MVC CONTROLLER VIEW MODEL

Slide 4

Slide 4 text

WHAT IS MVC CONTROLLER VIEW MODEL update

Slide 5

Slide 5 text

WHAT IS MVC CONTROLLER VIEW MODEL update notify

Slide 6

Slide 6 text

WHAT IS MVC CONTROLLER VIEW MODEL update update notify

Slide 7

Slide 7 text

WHAT IS MVC CONTROLLER VIEW MODEL user action update update notify

Slide 8

Slide 8 text

WE ARE DEVELOPERS ▸ A good developer can ▸ Notice problems ?

Slide 9

Slide 9 text

WE ARE DEVELOPERS ▸ A good developer can ▸ Notice problems ?

Slide 10

Slide 10 text

WE ARE DEVELOPERS ▸ A good developer can ▸ Notice problems ▸ Find existing solutions

Slide 11

Slide 11 text

WE ARE DEVELOPERS ▸ A good developer can ▸ Notice problems ▸ Find existing solutions MVVM+React

Slide 12

Slide 12 text

WE ARE DEVELOPERS ▸ A good developer can ▸ Notice problems ▸ Find existing solutions MVVM+React MVP

Slide 13

Slide 13 text

WE ARE DEVELOPERS ▸ A good developer can ▸ Notice problems ▸ Find existing solutions MVVM+React MVP Flux

Slide 14

Slide 14 text

WE ARE DEVELOPERS ▸ A good developer can ▸ Notice problems ▸ Find existing solutions MVVM+React MVP Redux Flux

Slide 15

Slide 15 text

WE ARE DEVELOPERS ▸ A good developer can ▸ Notice problems ▸ Find existing solutions ▸ Implement the solution MVVM+React MVP Redux Flux

Slide 16

Slide 16 text

“THINK BEFORE YOU CODE.” –someone’s teacher

Slide 17

Slide 17 text

TEXT EXAMPLE - PLAIN VIEW CONTROLLER class ViewController: UIViewController { @IBOutlet var avatarImageContainerView: UIView! @IBOutlet var avatarImageView: UIImageView! private var avatarDataTask: URLSessionTask? private var imageUrl: URL? override func viewDidLoad() { super.viewDidLoad() avatarImageView.clipsToBounds = true // Set shadow and border to avatarImageContainerView } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() avatarImageContainerView.layer.cornerRadius = min(avatarImageContainerView.frame.width, avatarImageContainerView.frame.height) avatarImageView.layer.cornerRadius = min(avatarImageView.frame.width, avatarImageView.frame.height) } private func setAvatarImageUrl(_ url: URL?) { avatarDataTask?.cancel() imageUrl = url avatarImageView.image = nil guard imageUrl != nil else { return } avatarDataTask = URLSession.shared .dataTask(with: imageUrl!) { [weak self] (data, res, err) in guard let data = data else { return } self?.avatarImageView.image = UIImage(data: data) } avatarDataTask?.resume() } }

Slide 18

Slide 18 text

UIVIEWCONTROLLER UIVIEW MODEL user action update update notify

Slide 19

Slide 19 text

UIVIEWCONTROLLER UIVIEW MODEL user action update update notify ?

Slide 20

Slide 20 text

developer.apple.com TEXT

Slide 21

Slide 21 text

Although it is possible to have views directly observe models to detect changes in state, it is best not to do so. A view object should always go through a mediating controller object to learn about changes in an model object. The reason is two-fold: developer.apple.com TEXT

Slide 22

Slide 22 text

Although it is possible to have views directly observe models to detect changes in state, it is best not to do so. A view object should always go through a mediating controller object to learn about changes in an model object. The reason is two-fold: • If you use the bindings mechanism to have view objects directly observe the properties of model objects, you bypass all the advantages that NSController and its subclasses give your application: selection and placeholder management as well as the ability to commit and discard changes. developer.apple.com TEXT

Slide 23

Slide 23 text

Although it is possible to have views directly observe models to detect changes in state, it is best not to do so. A view object should always go through a mediating controller object to learn about changes in an model object. The reason is two-fold: • If you use the bindings mechanism to have view objects directly observe the properties of model objects, you bypass all the advantages that NSController and its subclasses give your application: selection and placeholder management as well as the ability to commit and discard changes. • If you don't use the bindings mechanism, you have to subclass an existing view class to add the ability to observe change notifications posted by a model object. developer.apple.com TEXT

Slide 24

Slide 24 text

Although it is possible to have views directly observe models to detect changes in state, it is best not to do so. A view object should always go through a mediating controller object to learn about changes in an model object. The reason is two-fold: • If you use the bindings mechanism to have view objects directly observe the properties of model objects, you bypass all the advantages that NSController and its subclasses give your application: selection and placeholder management as well as the ability to commit and discard changes. • If you don't use the bindings mechanism, you have to subclass an existing view class to add the ability to observe change notifications posted by a model object. developer.apple.com TEXT Use NSController(inside AppKit)/ViewController

Slide 25

Slide 25 text

Although it is possible to have views directly observe models to detect changes in state, it is best not to do so. A view object should always go through a mediating controller object to learn about changes in an model object. The reason is two-fold: • If you use the bindings mechanism to have view objects directly observe the properties of model objects, you bypass all the advantages that NSController and its subclasses give your application: selection and placeholder management as well as the ability to commit and discard changes. • If you don't use the bindings mechanism, you have to subclass an existing view class to add the ability to observe change notifications posted by a model object. developer.apple.com TEXT Use NSController(inside AppKit)/ViewController IT IS SO GOOD

Slide 26

Slide 26 text

Although it is possible to have views directly observe models to detect changes in state, it is best not to do so. A view object should always go through a mediating controller object to learn about changes in an model object. The reason is two-fold: • If you use the bindings mechanism to have view objects directly observe the properties of model objects, you bypass all the advantages that NSController and its subclasses give your application: selection and placeholder management as well as the ability to commit and discard changes. • If you don't use the bindings mechanism, you have to subclass an existing view class to add the ability to observe change notifications posted by a model object. developer.apple.com TEXT Use NSController(inside AppKit)/ViewController IT IS SO GOOD Fine, or you can subclass yourself.

Slide 27

Slide 27 text

TEXT MVC ON MACOS UIVIEWCONTROLLER / NSCONTROLLER UIVIEW MODEL user action update update notify

Slide 28

Slide 28 text

TEXT TRADITIONAL MVC UIVIEWCONTROLLER / NSCONTROLLER UIVIEW SUBCLASS MODEL user action update update notify Get changed state

Slide 29

Slide 29 text

UIVIEWCONTROLLER AN OBJECT THAT MANAGES A VIEW HIERARCHY FOR YOUR UIKIT APP.

Slide 30

Slide 30 text

UIVIEWCONTROLLER

Slide 31

Slide 31 text

UIVIEWCONTROLLER ▸ Presenting/dismissing ▸ View life cycle ▸ View transitioning ▸ Handle view events

Slide 32

Slide 32 text

UIVIEW AN OBJECT THAT MANAGES THE CONTENT FOR A RECTANGULAR AREA ON THE SCREEN.

Slide 33

Slide 33 text

UIVIEW

Slide 34

Slide 34 text

UIVIEW ▸ View hierarchy management

Slide 35

Slide 35 text

UIVIEW ▸ View hierarchy management ▸ Control event management

Slide 36

Slide 36 text

UIVIEW ▸ View hierarchy management ▸ Control event management ▸ Rendering attributes

Slide 37

Slide 37 text

UIVIEW ▸ View hierarchy management ▸ Control event management ▸ Rendering attributes ▸ Layout management

Slide 38

Slide 38 text

UIVIEW ▸ View hierarchy management ▸ Control event management ▸ Rendering attributes ▸ Layout management ▸ Animations

Slide 39

Slide 39 text

MAKE OUR UIVIEW

Slide 40

Slide 40 text

SUBCLASSING UIVIEW ▸ Move UIView dependent code from view controller ▸ Self-managed UIView UIVIEWCONTROLLER UIVIEW SETUP CODE

Slide 41

Slide 41 text

SUBCLASSING UIVIEW ▸ Move UIView dependent code from view controller ▸ Self-managed UIView UIVIEWCONTROLLER UIVIEW SETUP CODE

Slide 42

Slide 42 text

SUBCLASSING UIVIEW ▸ Move UIView dependent code from view controller ▸ Self-managed UIView UIVIEWCONTROLLER UIVIEW SETUP CODE EASIER TO TEST

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

UISlider

Slide 45

Slide 45 text

UISlider UISwitch

Slide 46

Slide 46 text

UISlider UISwitch UITextField

Slide 47

Slide 47 text

UISlider UISwitch UITextField UIButton

Slide 48

Slide 48 text

UISlider UISwitch UITextField UIButton UISegmentedControl

Slide 49

Slide 49 text

UISlider UISwitch UITextField UIStepper UIButton UISegmentedControl

Slide 50

Slide 50 text

EXAMPLE - PLAIN VIEW CONTROLLER class AvatarIconButton: UIButton { var userInfo: UserInfo! { didSet { UserRepository.getUserAvatar(userInfo) { [weak self] result in if case .success(let image) = result { self?.setImage(image, for: .normal) } } } } override func awakeFromNib() { super.awakeFromNib() // setup view settings } override func layoutSubviews() { super.layoutSubviews() layer.cornerRadius = min(frame.width, frame.height) / 2 if let imageView = imageView { imageView.layer.cornerRadius = min(imageView.frame.width, imageView.frame.height) / 2 } } }

Slide 51

Slide 51 text

EXAMPLE - PLAIN VIEW CONTROLLER class ViewController: UIViewController { @IBOutlet weak var avatarIconView: AvatarIconView! var userRepo: UserRepository = UserRepository() override func viewDidLoad() { super.viewDidLoad() userRepo.getUserInfo { [weak self] result in switch result { case .success(let userInfo): self?.avatarIconView.userInfo = userInfo case .failure(let err): print(err) } } } }

Slide 52

Slide 52 text

MAKE OUR UIVIEWCONTROLLER

Slide 53

Slide 53 text

SUBCLASS UIVIEWCONTROLLER ▸ Extract your model/networking logic from VC ▸ Understand your data source (async/sync/both) UIVIEWCONTROLLER NETWORKING MODEL

Slide 54

Slide 54 text

SUBCLASS UIVIEWCONTROLLER ▸ Extract your model/networking logic from VC ▸ Understand your data source (async/sync/both) UIVIEWCONTROLLER NETWORKING MODEL HARD TO UNIT TEST

Slide 55

Slide 55 text

SUBCLASS UIVIEWCONTROLLER ▸ Extract your model/networking logic from VC ▸ Understand your data source (async/sync/both) UIVIEWCONTROLLER NETWORKING MODEL CONTROLLER CODE ONLY

Slide 56

Slide 56 text

TEXT EXAMPLE - TABLE VIEW CONTROLLER class DataStore { private var titles = [String]() var count: Int { return titles.count } func data(from: IndexPath) -> String { return titles[from.row] } func move(from: IndexPath, to: IndexPath) { let compareResult = from.compare(to) guard compareResult != .orderedSame else { return } let item = titles.remove(at: from.row) titles.insert(item, at: (compareResult == .orderedAscending ? to.row - 1 : to.row)) } func remove(at: IndexPath) { titles.remove(at: at.row) } func createItem(at: IndexPath) { titles.insert("New Title", at: at.row) } }

Slide 57

Slide 57 text

TEXT EXAMPLE - TABLE VIEW CONTROLLER class SampleViewController: UITableViewController { let dataStore: DataStore init(_ dataStore: DataStore) { self.dataStore = dataStore super.init(nibName: nil, bundle: nil) } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) cell.textLabel?.text = dataStore.data(from: indexPath) return cell } override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { dataStore.move(from: sourceIndexPath, to: destinationIndexPath) } override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { dataStore.remove(at: indexPath) } else if editingStyle == .insert { dataStore.createItem(at: indexPath) } } }

Slide 58

Slide 58 text

MORE COMPLEX

Slide 59

Slide 59 text

TEXT COMPLEX VIEW CONTROLLER ▸ Use child VC to present contents ▸ Define protocol to pass model

Slide 60

Slide 60 text

TEXT COMPLEX VIEW CONTROLLER ▸ Use child VC to present contents ▸ Define protocol to pass model

Slide 61

Slide 61 text

TEXT EXAMPLE - CHILD VIEW CONTROLLER class ChildViewController: UIViewController { private var modelStore: MyModelStore! override func didMove(toParent parent: UIViewController?) { var dataProviderVC = parent while dataProviderVC != nil { if let dataProviderVC = dataProviderVC as? MyDataProviderProtocol { modelStore = dataProviderVC.getModelStore() break } dataProviderVC = dataProviderVC?.parent } // Unable to find data provider } }

Slide 62

Slide 62 text

MORE COMPLEX

Slide 63

Slide 63 text

TEXT COMPLEX VIEW CONTROLLER ▸ Design view controller hierarchy ▸ Present child VC dynamically ▸ Child VC has dynamic frame size

Slide 64

Slide 64 text

TEXT COMPLEX VIEW CONTROLLER ▸ Design view controller hierarchy ▸ Present child VC dynamically ▸ Child VC has dynamic frame size

Slide 65

Slide 65 text

UITABLEVIEWCONTROLLER UITABLEVIEW

Slide 66

Slide 66 text

UITABLEVIEWCONTROLLER MODEL UITABLEVIEW

Slide 67

Slide 67 text

UITABLEVIEWCONTROLLER REPOSITORY MODEL UITABLEVIEW

Slide 68

Slide 68 text

UITABLEVIEWCONTROLLER REPOSITORY MODEL UITABLEVIEW

Slide 69

Slide 69 text

UITABLEVIEWCONTROLLER REPOSITORY MODEL UITABLEVIEW UITABLEVIEWCELL

Slide 70

Slide 70 text

UITABLEVIEWCONTROLLER REPOSITORY MODEL UITABLEVIEW UITABLEVIEWCELL UIVIEWCONTROLLER

Slide 71

Slide 71 text

UITABLEVIEWCONTROLLER REPOSITORY MODEL UITABLEVIEW UITABLEVIEWCELL UIVIEWCONTROLLER MODEL

Slide 72

Slide 72 text

TEXT EXAMPLE - EMBEDDED VIEW CONTROLLER IN CELL private class DataStore { private var viewControllers = [IndexPath: UIViewController]() func generateViewController(at: IndexPath) -> UIViewController { let viewController = ChildViewController() viewControllers[at] = viewController return viewController } func removeViewController(at: IndexPath) -> UIViewController { return viewControllers.removeValue(forKey: at)! } }

Slide 73

Slide 73 text

TEXT EXAMPLE - EMBEDDED VIEW CONTROLLER IN CELL class ParentViewController: UITableViewController { fileprivate let dataStore = DataStore() override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { let viewController = dataStore.generateViewController(at: indexPath) addChild(viewController) viewController.didMove(toParent: self) cell.contentView.addSubview(viewController.view) } override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { let viewController = dataStore.removeViewController(at: indexPath) viewController.view.removeFromSuperview() viewController.willMove(toParent: nil) viewController.removeFromParent() } }

Slide 74

Slide 74 text

TEXT EXAMPLE - EMBEDDED VIEW CONTROLLER IN CELL class ParentTableViewCell: UITableViewCell { func appendCustomView(_ view: UIView) { // setup auto layout constraints view.frame = contentView.bounds contentView.addSubview(view) } }

Slide 75

Slide 75 text

TEXT SUMMARY ▸ MVC can be clean and naive ▸ You can create general and reusable UI ▸ There is no best design strategy for every app, select what meets your need ▸ MVVM(+React), Flux, Redux, … are still pretty awesome!

Slide 76

Slide 76 text

͋Γ͕ͱ͏͍͟͝·͢