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

NSBarcelona - Making UITableView great again

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →

NSBarcelona - Making UITableView great again

None of us thought that tableview layouts coud fun or easy. After we sat down and solved the issues once and for all, we changed our minds. In this talk we are going tell you all how we did it.

Avatar for Alfredo Delli Bovi

Alfredo Delli Bovi

November 21, 2016

Other Decks in Programming

Transcript

  1. TableView is fast 0ms 200ms 400ms 600ms 800ms Cells count

    0 20 40 60 80 100 UIStackView UICollectionViewFlowLayout + Auto Layout UICollectionViewFlowLayout + Manual Layout UITableView + Manual Layout UITableView + Auto Layout http://layoutkit.org/benchmarks/
  2. static let identifier = “CellIdentifier" override func viewDidLoad() { super.viewDidLoad()

    self.tableView.register(Cell.self, forCellReuseIdentifier: identifier) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) // Configure the cell... return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action }
  3. static let identifier = “CellIdentifier" override func viewDidLoad() { super.viewDidLoad()

    self.tableView.register(Cell.self, forCellReuseIdentifier: identifier) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) // Configure the cell... return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action }
  4. static let identifier = “CellIdentifier" override func viewDidLoad() { super.viewDidLoad()

    self.tableView.register(Cell.self, forCellReuseIdentifier: identifier) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) // Configure the cell... return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action }
  5. static let identifier = “CellIdentifier" override func viewDidLoad() { super.viewDidLoad()

    self.tableView.register(Cell.self, forCellReuseIdentifier: identifier) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) // Configure the cell... return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action }
  6. static let identifier = “CellIdentifier" override func viewDidLoad() { super.viewDidLoad()

    self.tableView.register(Cell.self, forCellReuseIdentifier: identifier) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) // Configure the cell... return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action }
  7. override func viewDidLoad() { super.viewDidLoad() self.tableView.register(Cell.self, forCellReuseIdentifier: "CellIdentifier") self.tableView.register(Cell2.self, forCellReuseIdentifier:

    "CellIdentifier2") } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: UITableViewCell if (shouldReturnTheFirstTypeOfCell) { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) // Configure the cell 1... } else { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier2", for: indexPath) // Configure the cell 2... } return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if (shouldReturnTheFirstTypeOfCell) { return 44.0 } else { return 120.0 } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action }
  8. override func viewDidLoad() { super.viewDidLoad() self.tableView.register(Cell.self, forCellReuseIdentifier: "CellIdentifier") self.tableView.register(Cell2.self, forCellReuseIdentifier:

    "CellIdentifier2") } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: UITableViewCell if (shouldReturnTheFirstTypeOfCell) { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) // Configure the cell 1... } else { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier2", for: indexPath) // Configure the cell 2... } return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if (shouldReturnTheFirstTypeOfCell) { return 44.0 } else { return 120.0 } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action }
  9. override func viewDidLoad() { super.viewDidLoad() self.tableView.register(Cell.self, forCellReuseIdentifier: "CellIdentifier") self.tableView.register(Cell2.self, forCellReuseIdentifier:

    “CellIdentifier2") self.tableView.register(Header.self, forHeaderFooterViewReuseIdentifier: “Header") self.tableView.register(Footer.self, forHeaderFooterViewReuseIdentifier: “Footer") } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: UITableViewCell if (shouldReturnTheFirstTypeOfCell) { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) // Configure the cell 1... } else { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier2", for: indexPath) // Configure the cell 2... } return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if (shouldReturnTheFirstTypeOfCell) { return 44.0 } else { return 120.0 } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return tableView.dequeueReusableHeaderFooterView(withIdentifier: “Header”) } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return tableView.dequeueReusableHeaderFooterView(withIdentifier: “Footer”) } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 20.0 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 60.0 }
  10. override func viewDidLoad() { super.viewDidLoad() self.tableView.register(Cell.self, forCellReuseIdentifier: "CellIdentifier") self.tableView.register(Cell2.self, forCellReuseIdentifier:

    “CellIdentifier2") self.tableView.register(Header.self, forHeaderFooterViewReuseIdentifier: “Header") self.tableView.register(Footer.self, forHeaderFooterViewReuseIdentifier: “Footer") } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: UITableViewCell if (shouldReturnTheFirstTypeOfCell) { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) // Configure the cell 1... } else { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier2", for: indexPath) // Configure the cell 2... } return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if (shouldReturnTheFirstTypeOfCell) { return 44.0 } else { return 120.0 } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return tableView.dequeueReusableHeaderFooterView(withIdentifier: “Header”) } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return tableView.dequeueReusableHeaderFooterView(withIdentifier: “Footer”) } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 20.0 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 60.0 }
  11. override func viewDidLoad() { super.viewDidLoad() self.tableView.register(Cell.self, forCellReuseIdentifier: "CellIdentifier") self.tableView.register(Cell2.self, forCellReuseIdentifier:

    “CellIdentifier2") self.tableView.register(Header.self, forHeaderFooterViewReuseIdentifier: “Header") self.tableView.register(Footer.self, forHeaderFooterViewReuseIdentifier: “Footer") } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: UITableViewCell if (shouldReturnTheFirstTypeOfCell) { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) // Configure the cell 1... } else { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier2", for: indexPath) // Configure the cell 2... } return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if (shouldReturnTheFirstTypeOfCell) { return 44.0 } else { return 120.0 } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return tableView.dequeueReusableHeaderFooterView(withIdentifier: “Header”) } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return tableView.dequeueReusableHeaderFooterView(withIdentifier: “Footer”) } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 20.0 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 60.0 }
  12. override func viewDidLoad() { super.viewDidLoad() self.tableView.register(Cell.self, forCellReuseIdentifier: "CellIdentifier") self.tableView.register(Cell2.self, forCellReuseIdentifier:

    “CellIdentifier2") self.tableView.register(Header.self, forHeaderFooterViewReuseIdentifier: “Header") self.tableView.register(Footer.self, forHeaderFooterViewReuseIdentifier: “Footer") } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: UITableViewCell if (shouldReturnTheFirstTypeOfCell) { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) // Configure the cell 1... } else { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier2", for: indexPath) // Configure the cell 2... } return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if (shouldReturnTheFirstTypeOfCell) { return 44.0 } else { return 120.0 } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return tableView.dequeueReusableHeaderFooterView(withIdentifier: “Header”) } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return tableView.dequeueReusableHeaderFooterView(withIdentifier: “Footer”) } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 20.0 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 60.0 }
  13. override func viewDidLoad() { super.viewDidLoad() self.tableView.register(Cell.self, forCellReuseIdentifier: "CellIdentifier") self.tableView.register(Cell2.self, forCellReuseIdentifier:

    “CellIdentifier2") self.tableView.register(Header.self, forHeaderFooterViewReuseIdentifier: “Header") self.tableView.register(Footer.self, forHeaderFooterViewReuseIdentifier: “Footer") } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: UITableViewCell if (shouldReturnTheFirstTypeOfCell) { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) // Configure the cell 1... } else { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier2", for: indexPath) // Configure the cell 2... } return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if (shouldReturnTheFirstTypeOfCell) { return 44.0 } else { return 120.0 } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return tableView.dequeueReusableHeaderFooterView(withIdentifier: “Header”) } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return tableView.dequeueReusableHeaderFooterView(withIdentifier: “Footer”) } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 20.0 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 60.0 }
  14. override func viewDidLoad() { super.viewDidLoad() self.tableView.register(Cell.self, forCellReuseIdentifier: "CellIdentifier") self.tableView.register(Cell2.self, forCellReuseIdentifier:

    “CellIdentifier2") self.tableView.register(Header.self, forHeaderFooterViewReuseIdentifier: “Header") self.tableView.register(Footer.self, forHeaderFooterViewReuseIdentifier: “Footer") } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: UITableViewCell if (shouldReturnTheFirstTypeOfCell) { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) // Configure the cell 1... } else { cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier2", for: indexPath) // Configure the cell 2... } return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if (shouldReturnTheFirstTypeOfCell) { return 44.0 } else { return 120.0 } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // Do some action } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return tableView.dequeueReusableHeaderFooterView(withIdentifier: “Header”) } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return tableView.dequeueReusableHeaderFooterView(withIdentifier: “Footer”) } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 20.0 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 60.0 }
  15. public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?

    public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int public func numberOfSections(in tableView: UITableView) -> Int public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat public func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat public func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat Those are 11 methods!
  16. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {


    switch row {
 case 0:
 return firstCell
 case 1...array.count:
 return dataCell
 case array.count+1 where folded == false:
 return showMoreCell
 case array.count+2,
 array.count+1 where folded == true:
 return specialCell
 default: nil!
 }

  17. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {


    switch row {
 case 0:
 return firstCell
 case 1...array.count:
 return dataCell
 case array.count+1 where folded == false:
 return showMoreCell
 case array.count+2,
 array.count+1 where folded == true:
 return specialCell
 default: nil!
 }
 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
 return array.count + (folded ? 2 : 3)
 }

  18. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {


    switch row {
 case 0:
 return firstCell
 case 1...array.count:
 return dataCell
 case array.count+1 where folded == false:
 return showMoreCell
 case array.count+2,
 array.count+1 where folded == true:
 return specialCell
 default: nil!
 }
 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
 return array.count + (folded ? 2 : 3)
 }
 if (folded) {
 tableView.deleteRows(at: [IndexPath(row: array.count + 1, section: 0)], with: .fade)
 } else {
 tableView.insertRows(at: [IndexPath(row: array.count + 1, section: 0)], with: .fade)
 }
  19. Boilerplate Complex layout are hard Small change = Rewrite code

    + No reusability Manual insertions or reloadData
  20. Calculate the height Drawing the cell Number of sections Number

    of rows in a section Managing states Registering cells Registering Headers Drawing header Interaction
  21. Delegate Calculate the height Drawing the cell Number of sections

    Number of rows in a section Managing states Registering cells Registering Headers Drawing header Interaction
  22. Calculate the height Drawing the cell Number of sections Number

    of rows in a section Managing states Registering cells Registering Headers Drawing header Interaction
  23. TableViewManager Section Section Section Drawer Drawer Drawer Item Item Item

    Header Footer UITableHeaderFooterView UITableViewCell UITableHeaderFooterView
  24. protocol TableViewManager: UITableViewDelegate, UITableViewDataSource {
 var sections: [Section] { get

    set }
 }
 protocol Section {
 var items: [Item] { get set }
 var header: HeaderFooter? { get }
 var footer: HeaderFooter? { get }
 }

  25. protocol TableViewManager: UITableViewDelegate, UITableViewDataSource {
 var sections: [Section] { get

    set }
 }
 protocol Section {
 var items: [Item] { get set }
 var header: HeaderFooter? { get }
 var footer: HeaderFooter? { get }
 }
 protocol Item {
 var drawer: CellDrawer.Type { get }
 var height: Height? { get }
 }

  26. protocol TableViewManager: UITableViewDelegate, UITableViewDataSource {
 var sections: [Section] { get

    set }
 }
 protocol Section {
 var items: [Item] { get set }
 var header: HeaderFooter? { get }
 var footer: HeaderFooter? { get }
 }
 protocol Item {
 var drawer: CellDrawer.Type { get }
 var height: Height? { get }
 }
 protocol HeaderFooter {
 var drawer: HeaderFooterDrawer.Type { get }
 var height: Height? { get }
 }
  27. protocol CellDrawer { } static var type: CellType { get

    }
 typealias CellType = NibClassType<UITableViewCell> enum NibClassType<T> { case nib(UINib, T.Type) case `class`(T.Type) }
  28. protocol CellDrawer { } static var type: CellType { get

    }
 static func draw(_ cell: UITableViewCell, with item: Item)

  29. protocol CellDrawer { } static var type: CellType { get

    }
 static func draw(_ cell: UITableViewCell, with item: Item)
 static func cell(in tableView: UITableView, with item: Item, for indexPath: IndexPath) -> UITableViewCell
  30. extension CellDrawer {
 static func cell(in manager: TableViewManager, with item:

    Item, for indexPath: IndexPath) -> UITableViewCell { let cell = manager.tableView.dequeueReusableCell(withIdentifier: self.type.reusableIdentifier, for: indexPath) return cell
 }
 }
  31. extension CellDrawer {
 static func cell(in manager: TableViewManager, with item:

    Item, for indexPath: IndexPath) -> UITableViewCell { let cell = manager.tableView.dequeueReusableCell(withIdentifier: self.type.reusableIdentifier, for: indexPath) return cell
 }
 } extension NibClassType {
 var reusableIdentifier: String {
 switch self { case .class(let cellClass): return String(describing: cellClass) case .nib(_, let cellClass): return String(describing: cellClass) } } }
  32. enum Height { case dynamic(CGFloat) case `static`(CGFloat)
 } extension Height

    { var estimated: CGFloat { switch self { case .static(let value): return value case .dynamic(let value): return value } } var height: CGFloat { switch self { case .static(let value): return value case .dynamic(_): return UITableViewAutomaticDimension } } }
  33. public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?

    public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int public func numberOfSections(in tableView: UITableView) -> Int public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat public func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat public func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat
  34. *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable

    to dequeue a cell with identifier UITableViewCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard' *** First throw call stack: ( 0 CoreFoundation 0x0000000108666d4b __exceptionPreprocess + 171 1 libobjc.A.dylib 0x00000001058af21e objc_exception_throw + 48 2 CoreFoundation 0x000000010866ae42 +[NSException raise:format:arguments:] + 98 3 Foundation 0x000000010544471d -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 195 4 UIKit 0x00000001065eb8f4 -[UITableView dequeueReusableCellWithIdentifier:forIndexPath:] + 259
  35. extension UITableView {
 func register(_ type: NibClassType<UITableViewCell>) {
 switch type

    {
 case .class(let class):
 register(class, forCellReuseIdentifier: type.reusableIdentifier)
 case .nib(let nib, _):
 register(nib, forCellReuseIdentifier: type.reusableIdentifier)
 }
 }
 }

  36. extension UITableView {
 func register(_ type: NibClassType<UITableViewCell>) {
 switch type

    {
 case .class(let class):
 register(class, forCellReuseIdentifier: type.reusableIdentifier)
 case .nib(let nib, _):
 register(nib, forCellReuseIdentifier: type.reusableIdentifier)
 }
 }
 }
 extension Section {
 func register(in manager: TableViewManager) {
 if case .view(let header) = header {
 manager.tableView.register(header.drawer.type)
 }
 if case .view(let footer) = footer {
 manager.tableView.register(footer.drawer.type)
 }
 items.forEach {
 manager.tableView.register($0.drawer.type)
 }
 }
 }

  37. extension UITableView {
 func register(_ type: NibClassType<UITableViewCell>) {
 switch type

    {
 case .class(let class):
 register(class, forCellReuseIdentifier: type.reusableIdentifier)
 case .nib(let nib, _):
 register(nib, forCellReuseIdentifier: type.reusableIdentifier)
 }
 }
 }
 extension Section {
 func register(in manager: TableViewManager) {
 if case .view(let header) = header {
 manager.tableView.register(header.drawer.type)
 }
 if case .view(let footer) = footer {
 manager.tableView.register(footer.drawer.type)
 }
 items.forEach {
 manager.tableView.register($0.drawer.type)
 }
 }
 }
 sections.forEach { $0.register(in: self) }
  38. A B

  39. enum ArrayChanges {
 case inserts([Int])
 case deletes([Int])
 case moves([(Int, Int)])


    case beginUpdates
 case endUpdates
 }
 Longest Common Subsequence
  40. enum ArrayChanges {
 case inserts([Int])
 case deletes([Int])
 case moves([(Int, Int)])


    case beginUpdates
 case endUpdates
 }
 extension MutableCollection {
 mutating func replace(with array: [Element]) { }
 } Longest Common Subsequence
  41. protocol Stateful: Section { associatedtype State var currentState: State {

    get set } 
 func items(for state: State) -> [Item] func transition(to newState: State)
 }

  42. protocol Stateful: Section { associatedtype State var currentState: State {

    get set } 
 func items(for state: State) -> [Item] func transition(to newState: State)
 }

  43. protocol Stateful: Section { associatedtype State var currentState: State {

    get set } 
 func items(for state: State) -> [Item] func transition(to newState: State)
 }

  44. protocol Stateful: Section { associatedtype State var currentState: State {

    get set } 
 func items(for state: State) -> [Item] func transition(to newState: State)
 }

  45. protocol Stateful: Section { associatedtype State var currentState: State {

    get set } 
 func items(for state: State) -> [Item] func transition(to newState: State)
 }
 extension Stateful {
 func transition(to newState: State) {
 items.replace(with: items(for: newState))
 currentState = newState
 }
 }
  46. class OurSection: Section, Stateful {
 var currentState: State
 enum State

    {
 case folded, unfolded
 }
 func items(for state: State) -> [Item]
 switch state {
 case .folded:
 return [FirstItem(), DataItem(), ShowMoreItem()] 
 case .unfolded:
 return [FirstItem(), DataItem(), SpecialItem(), ShowMoreItem()]
 }
 }
 }
  47. class OurSection: Section, Stateful {
 var currentState: State
 
 enum

    State {
 case folded, unfolded
 }
 func items(for state: State) -> [Item]
 switch state {
 
 
 
 
 }
 }
 } case .folded:
 return [FirstItem(), DataItem(), ShowMoreItem()] 
 case .unfolded:
 return [FirstItem(), DataItem(), SpecialItem(), ShowMoreItem()]
  48. class OurSection: Section, Stateful {
 var currentState: State
 
 enum

    State {
 case folded, unfolded
 }
 func items(for state: State) -> [Item]
 switch state {
 
 
 
 
 }
 }
 } case .folded: return [ShowMoreItem(), FirstItem(), DataItem()] case .unfolded: return [ShowMoreItem(), SpecialItem() FirstItem(), DataItem()]
  49. Boilerplate Complex layout are hard Small change = Rewrite code

    + No reusability Manual insertions or reloadData
  50. Boilerplate Complex layout are hard Small change = Rewrite code

    + No reusability Manual insertions or reloadData
  51. No more delegate Complex layout are hard Small change =

    Rewrite code + No reusability Manual insertions or reloadData
  52. No more delegate ☀ Complex layout are easy Small change

    = Rewrite code + No reusability Manual insertions or reloadData
  53. No more delegate ☀ Complex layout are easy Change everything

    with few lines + No reusability Manual insertions or reloadData
  54. No more delegate ☀ Complex layout are easy Change everything

    with few lines 1 Items, sections, headers Manual insertions or reloadData
  55. No more delegate ☀ Complex layout are easy Change everything

    with few lines 1 Items, sections, headers ❤ Automatic diffs and animations
  56. No more delegate ☀ Complex layout are easy Change everything

    with few lines 1 Items, sections, headers ❤ Automatic diffs and animations