// // TableSectionDataDiffing.swift // Fabric // // Created by Javier Soto on 1/10/16. // Copyright © 2016 Fabric. All rights reserved. // import UIKit protocol TableSectionType { associatedtype AssociatedTableRowType: TableRowType var rows: [AssociatedTableRowType] { get } var title: String? { get } } extension TableSectionType { var title: String? { return nil } } protocol TableRowType { } enum TableUpdateOperation { case ReloadSections(NSIndexSet) case InsertSections(NSIndexSet) case DeleteSections(NSIndexSet) case InsertRows([NSIndexPath]) case DeleteRows([NSIndexPath]) case ReloadRows([NSIndexPath]) } struct AnySection>: TableSectionType, Equatable { let title: String? let rows: [AssociatedTableRowType] init(title: String? = nil, rows: [AssociatedTableRowType]) { self.title = title self.rows = rows } } func ==(lhs: AnySection, rhs: AnySection) -> Bool { return lhs.title == rhs.title && lhs.rows == rhs.rows } struct TableSectionDataDiffing { static func tableOperationsToUpdateFromRows(rows: [R], toRows newRows: [R]) -> [TableUpdateOperation] { return self.tableOperationsToUpdateFromSections(sections: [AnySection(rows: rows)], toSections: [AnySection(rows: newRows)]) } static func tableOperationsToUpdateFromSections(sections oldSections: [S], toSections newSections: [S]) -> [TableUpdateOperation] { guard newSections != oldSections else { return [] } var operations: [TableUpdateOperation] = [] // Section changes: let oldSectionCount = oldSections.count let newSectionCount = newSections.count let sectionCountDelta = newSectionCount - oldSectionCount if sectionCountDelta > 0 { operations.append(.InsertSections(NSIndexSet(indexesInRange: NSMakeRange(oldSectionCount, sectionCountDelta)))) } else if sectionCountDelta < 0 { operations.append(.DeleteSections(NSIndexSet(indexesInRange: NSMakeRange(newSectionCount, -sectionCountDelta)))) } let commonCount = min(oldSectionCount, newSectionCount) let sectionsThatChanged = newSections.prefix(commonCount).enumerate().filter { $0.element != oldSections[$0.index] } sectionsThatChanged.map { $0.index }.forEach { sectionIndex in let titleChanged = oldSections[sectionIndex].title != newSections[sectionIndex].title if titleChanged { operations.append(.ReloadSections(NSIndexSet(index: sectionIndex))) } // Row changes in this section let oldRowsInSection = oldSections[sectionIndex].rows let newRowsInSection = newSections[sectionIndex].rows let oldRowCount = oldRowsInSection.count let newRowCount = newRowsInSection.count let rowCountDelta = newRowCount - oldRowCount if rowCountDelta > 0 { let indexPaths = (oldRowCount.. UITableViewRowAnimation extension TableUpdateOperation { static func noAnimations(_: TableUpdateOperation) -> UITableViewRowAnimation { return .None } static func defaultAnimations(operation: TableUpdateOperation) -> UITableViewRowAnimation { switch operation { case .ReloadSections: return .Fade case .InsertSections: return .None case .DeleteSections: return .Fade case .InsertRows: return .Fade case .DeleteRows: return .None case .ReloadRows: return .Fade } } } extension UITableView { private func applyTableOperation(operation: TableUpdateOperation, withAnimation animation: UITableViewRowAnimation) { switch operation { case let .ReloadSections(sections): self.reloadSections(sections, withRowAnimation: animation) case let .InsertSections(sections): self.insertSections(sections, withRowAnimation: animation) case let .DeleteSections(sections): self.deleteSections(sections, withRowAnimation: animation) case let .InsertRows(indexPaths): self.insertRowsAtIndexPaths(indexPaths, withRowAnimation: animation) case let .DeleteRows(indexPaths): self.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: animation) case let .ReloadRows(indexPaths): self.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: animation) } } func applyTableOperations(tableOperations: [TableUpdateOperation], withAnimations animations: TableUpdateOperationAnimations = TableUpdateOperation.defaultAnimations, completion: (() -> ())? = nil) { guard !tableOperations.isEmpty else { return } if let completion = completion { CATransaction.begin() CATransaction.setCompletionBlock(completion) } self.beginUpdates() tableOperations.forEach { operation in let animation = animations(operation) self.applyTableOperation(operation, withAnimation: animation) } self.endUpdates() if completion != nil { CATransaction.commit() } } }