Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Cellular View Controllers

Eeb061c8b2816b771920da1b3e7904a3?s=47 Swift India
November 24, 2018

Cellular View Controllers

Eeb061c8b2816b771920da1b3e7904a3?s=128

Swift India

November 24, 2018
Tweet

Transcript

  1. Cellular View Controllers Robin Malhotra

  2. Preface

  3. None
  4. A few apps

  5. None
  6. Let’s build our own! • UIScrollView subclass (because it scrolls)

    • UICollectionView/UITableView for vertical scrolling • UICollectionView inside each cell • Separate out your data sources if necessary (I recommend something else entirely)
  7. • LPT:

  8. What about horizontal scrolling cells? • Pagination? • UI updates?

    • Loading states? • Network Requests?
  9. The SomethingManager • Figure out when the cell is being

    displayed, and perform a start action (make an API call, show a loader etc) • When the cell moves out of display area, get rid of resources • When you’re reaching the end of a horizontal list, talk to the network client and update state
  10. class EmbeddableCellDingus<T: UIView & Reusable>: UITableViewCell, Reusable { func configure(with

    dingus: T) { self.contentView.subviews.forEach{ $0.removeFromSuperview() } self.contentView.addSubview(dingus) dingus.alignEdges(to: self.contentView) dingus.translatesAutoresizingMaskIntoConstraints = false } }
  11. class SomethingManager { func contentViewHasLoaded() { } func contentViewWillAppear() {

    } func contentViewDidDisappear() { } func contentViewDidLayoutSubviews() { //do "large" layouts based on state changes } }
  12. extension ViewController { override func tableView(_ tableView: UITableView, willDisplay cell:

    UITableViewCell, forRowAt indexPath: IndexPath) { // *Uncomfortable typecasting intensifies* // *will have to do this for every type* (cell as? EmbeddableCellDingus<ThumbnailView>)?.manager.contentViewWillAppear() } override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { (cell as? EmbeddableCellDingus<ThumbnailView>)?.manager.contentViewDidDisappear() } }
  13. State/Model SomethingManager Cell Notify State updates User 
 Interactions Notify

  14. None
  15. You didn’t really think I’d name a class “SomethingManager”, would

    you? © philipedwards
  16. None
  17. State/Model SomethingManager Cell Notify State updates User 
 Interactions Notify

  18. SomethingManager func contentViewHasLoaded() func contentViewIsGoingToAppear() func contentViewHasDisappeared() func contentViewHasLayoutSubviews()

  19. SomethingManager func contentViewDidLoad() func contentViewWillAppear() func contentViewDidDisappear() func contentViewDidLayoutSubviews()

  20. SomethingManager func viewDidLoad() func viewWillAppear() func viewDidDisappear() func viewDidLayoutSubviews()

  21. State/Model SomethingManager Cell Notify State updates User 
 Interactions Notify

  22. State/Model UIViewController Cell Notify State updates User 
 Interactions Notify

  23. State/Model CellController Cell Notify State updates User 
 Interactions Notify

  24. State/Model CellularController Cell Notify State updates User 
 Interactions Notify

  25. UITableViewCell → View Controller Containment → UIViewController

  26. None
  27. Prepare for…..reuse? • ViewDidLoad/Appear still work as expected, you need

    to make some extra work by querying the VC’s view for a sizeThatFits/constrainedSize • Every contained VC would have to support a “nil” state (where it’s ready to be reused). In your prepareForReuse, set that “nil” state and in your “configure” method, set the correct state. • ……Prepare for lots of reuse bugs and code weirdness because UIKit wasn’t really built for something like this
  28. Reuse protocol NullifiableForReuse { associatedtype State associatedtype CreationArgs func nullifyState()

    // It's wrong to assume VCs are initialized via nibName:Bundle func configure(with state: State) static var create: (CreationArgs) -> Self { get set } var intrinsicContentSize: CGSize { get } func sizeThatFits(_ size: CGSize) -> CGSize }
  29. class ViewControllerEmbeddingCell<T: UIViewController & NullifiableForReuse>: UITableViewCell, Reusable { var vc:

    T? override func prepareForReuse() { self.vc?.nullifyState() } func configure(with state: T.State, creationArgs: T.CreationArgs) { let vc = self.vc ?? T.create(creationArgs) vc.configure(with: state) self.contentView.subviews.forEach{ $0.removeFromSuperview() } self.contentView.addSubview(vc.view) vc.view.translatesAutoresizingMaskIntoConstraints = false vc.view.alignEdges(to: self.contentView) self.vc = vc } override var intrinsicContentSize: CGSize { return vc?.intrinsicContentSize ?? .zero } override func sizeThatFits(_ size: CGSize) -> CGSize { return vc?.sizeThatFits(size) ?? .zero } }
  30. Register Site override func viewDidLoad() { super.viewDidLoad() self.view.addSubview(tableView) tableView.dataSource =

    self tableView.register(ViewControllerEmbeddingCell<RedViewController> .self, forCellReuseIdentifier: ViewControllerEmbeddingCell<RedViewController>.reuseIdentifier) tableView.register(ViewControllerEmbeddingCell<BlueViewController >.self, forCellReuseIdentifier: ViewControllerEmbeddingCell<BlueViewController>.reuseIdentifier) }
  31. Creating a cell func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)

    -> UITableViewCell { let tryMakingCell: () -> UITableViewCell? = { switch indexPath.row % 2 == 0 { case true: let cell = tableView.dequeueReusableCell(withIdentifier: ViewControllerEmbeddingCell<RedViewController>.reuseIdentifier, for: indexPath) as? ViewControllerEmbeddingCell<RedViewController> cell?.configure(with: ("\(indexPath.row)"), creationArgs: ()) return cell case false: let cell = tableView.dequeueReusableCell(withIdentifier: ViewControllerEmbeddingCell<BlueViewController>.reuseIdentifier, for: indexPath) as? ViewControllerEmbeddingCell<BlueViewController> cell?.configure(with: (), creationArgs: ()) return cell } } return tryMakingCell() ?? UITableViewCell() }
  32. None
  33. None
  34. None
  35. None
  36. None
  37. None
  38. class EmbeddableControllerCell<T: ASDisplayNode>: ASCellNode { let embeddedViewController: ASViewController<T> let insets:

    UIEdgeInsets init(viewController: ASViewController<T>, insets: UIEdgeInsets = .zero) { self.embeddedViewController = viewController self.insets = insets super.init() self.automaticallyManagesSubnodes = true (self.style.width, self.style.height) = (self.embeddedViewController.node.style.width, self.embeddedViewController.node.style.height) } override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { return ASInsetLayoutSpec(insets: insets, child: embeddedViewController.node) } }
  39. let cell = EmbeddableControllerCell(viewController: vc) // create View Controller return

    cell
  40. https://github.com/ codeOfRobin/ EmbeddedCellsDemo

  41. None
  42. None