Revisiting Massive View Controller

Revisiting Massive View Controller

2019/4/24に行われたLINE Developer meetup #53 in KYOTOでの登壇資料です

53850955f15249a1a9dc49df6113e400?s=128

LINE Developers

April 24, 2019
Tweet

Transcript

  1. MVC @ IOS SU PING-CHEN (PC) LINE Fukuoka LINE Developer

    meetup #53 in KYOTO
  2. ABOUT ME ▸ ોฑਉɹεΡɹͼʔΜͪ͐Μ ▸ Taiwanese ▸ iOS developer (6

    years) ▸ LINE Fukuoka iOS developer ▸ LINE઎͍ ▸ LINE app
  3. WHAT IS MVC CONTROLLER VIEW MODEL

  4. WHAT IS MVC CONTROLLER VIEW MODEL update

  5. WHAT IS MVC CONTROLLER VIEW MODEL update notify

  6. WHAT IS MVC CONTROLLER VIEW MODEL update update notify

  7. WHAT IS MVC CONTROLLER VIEW MODEL user action update update

    notify
  8. WE ARE DEVELOPERS ▸ A good developer can ▸ Notice

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

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

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

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

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

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

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

    problems ▸ Find existing solutions ▸ Implement the solution MVVM+React MVP Redux Flux
  16. “THINK BEFORE YOU CODE.” –someone’s teacher

  17. 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() } }
  18. UIVIEWCONTROLLER UIVIEW MODEL user action update update notify

  19. UIVIEWCONTROLLER UIVIEW MODEL user action update update notify ?

  20. developer.apple.com TEXT

  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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.
  27. TEXT MVC ON MACOS UIVIEWCONTROLLER / NSCONTROLLER UIVIEW MODEL user

    action update update notify
  28. TEXT TRADITIONAL MVC UIVIEWCONTROLLER / NSCONTROLLER UIVIEW SUBCLASS MODEL user

    action update update notify Get changed state
  29. UIVIEWCONTROLLER AN OBJECT THAT MANAGES A VIEW HIERARCHY FOR YOUR

    UIKIT APP.
  30. UIVIEWCONTROLLER

  31. UIVIEWCONTROLLER ▸ Presenting/dismissing ▸ View life cycle ▸ View transitioning

    ▸ Handle view events
  32. UIVIEW AN OBJECT THAT MANAGES THE CONTENT FOR A RECTANGULAR

    AREA ON THE SCREEN.
  33. UIVIEW

  34. UIVIEW ▸ View hierarchy management

  35. UIVIEW ▸ View hierarchy management ▸ Control event management

  36. UIVIEW ▸ View hierarchy management ▸ Control event management ▸

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

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

    Rendering attributes ▸ Layout management ▸ Animations
  39. MAKE OUR UIVIEW

  40. SUBCLASSING UIVIEW ▸ Move UIView dependent code from view controller

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

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

    ▸ Self-managed UIView UIVIEWCONTROLLER UIVIEW SETUP CODE EASIER TO TEST
  43. None
  44. UISlider

  45. UISlider UISwitch

  46. UISlider UISwitch UITextField

  47. UISlider UISwitch UITextField UIButton

  48. UISlider UISwitch UITextField UIButton UISegmentedControl

  49. UISlider UISwitch UITextField UIStepper UIButton UISegmentedControl

  50. 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 } } }
  51. 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) } } } }
  52. MAKE OUR UIVIEWCONTROLLER

  53. SUBCLASS UIVIEWCONTROLLER ▸ Extract your model/networking logic from VC ▸

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

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

    Understand your data source (async/sync/both) UIVIEWCONTROLLER NETWORKING MODEL CONTROLLER CODE ONLY
  56. 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) } }
  57. 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) } } }
  58. MORE COMPLEX

  59. TEXT COMPLEX VIEW CONTROLLER ▸ Use child VC to present

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

    contents ▸ Define protocol to pass model
  61. 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 } }
  62. MORE COMPLEX

  63. TEXT COMPLEX VIEW CONTROLLER ▸ Design view controller hierarchy ▸

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

    Present child VC dynamically ▸ Child VC has dynamic frame size
  65. UITABLEVIEWCONTROLLER UITABLEVIEW

  66. UITABLEVIEWCONTROLLER MODEL UITABLEVIEW

  67. UITABLEVIEWCONTROLLER REPOSITORY MODEL UITABLEVIEW

  68. UITABLEVIEWCONTROLLER REPOSITORY MODEL UITABLEVIEW

  69. UITABLEVIEWCONTROLLER REPOSITORY MODEL UITABLEVIEW UITABLEVIEWCELL

  70. UITABLEVIEWCONTROLLER REPOSITORY MODEL UITABLEVIEW UITABLEVIEWCELL UIVIEWCONTROLLER

  71. UITABLEVIEWCONTROLLER REPOSITORY MODEL UITABLEVIEW UITABLEVIEWCELL UIVIEWCONTROLLER MODEL

  72. 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)! } }
  73. 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() } }
  74. 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) } }
  75. 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!
  76. ͋Γ͕ͱ͏͍͟͝·͢