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

Cellular View Controllers

Swift India
November 24, 2018

Cellular View Controllers

Swift India

November 24, 2018
Tweet

More Decks by Swift India

Other Decks in Technology

Transcript

  1. 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)
  2. 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
  3. 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 } }
  4. class SomethingManager { func contentViewHasLoaded() { } func contentViewWillAppear() {

    } func contentViewDidDisappear() { } func contentViewDidLayoutSubviews() { //do "large" layouts based on state changes } }
  5. 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() } }
  6. 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
  7. 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 }
  8. 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 } }
  9. 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) }
  10. 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() }
  11. 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) } }