Skip to content

Instantly share code, notes, and snippets.

@bnickel
Created February 12, 2019 21:54
Show Gist options
  • Save bnickel/b0d6fe65ef14fa267e39be30a53e2d0a to your computer and use it in GitHub Desktop.
Save bnickel/b0d6fe65ef14fa267e39be30a53e2d0a to your computer and use it in GitHub Desktop.
Using a general purpose diff tool to handle complex table view transitions
fileprivate func replaceAndExpandComments(_ comments:[SEAPIComment], includeUpdates:Bool) {
let originalComments = displayedComments
let changes = compare(original: displayedComments.map({ $0.commentId }), modified: comments.map({ $0.commentId }))
self.comments = comments
self.displayedComments = comments
var changedIndexPaths:[IndexPath] = []
var deletedIndexPaths:[IndexPath] = []
var insertedIndexPaths:[IndexPath] = []
var originalOffset = 0
var finalOffset = 0
let cellRowOffset = self.cellRowOffset
DDLogInfo("Before: \(originalComments.map({ String($0.commentId) }).joined(separator: ", ")); after: \(comments.map({ String($0.commentId) }).joined(separator: ", "))")
for change in changes {
switch change.status {
case .deleted:
deletedIndexPaths += (0 ..< change.items.count).map { IndexPath(row: $0 + originalOffset + cellRowOffset, section: self.sectionIndex) }
originalOffset += change.items.count
case .noChange:
if includeUpdates {
changedIndexPaths += (0 ..< change.items.count).filter{ !SECommentTableViewCell.wouldRenderComment(originalComments[$0 + originalOffset], identicallyTo: comments[$0 + finalOffset]) }.map{ IndexPath(row: $0 + originalOffset + cellRowOffset, section: self.sectionIndex) }
}
originalOffset += change.items.count
finalOffset += change.items.count
case .inserted:
insertedIndexPaths += (0 ..< change.items.count).map { IndexPath(row: $0 + finalOffset + cellRowOffset, section: self.sectionIndex) }
finalOffset += change.items.count
}
}
if changedIndexPaths.count > 0 || deletedIndexPaths.count > 0 || insertedIndexPaths.count > 0 {
DDLogInfo("Changing: \(changedIndexPaths.map({ String($0.row) }).joined(separator: ", "))")
DDLogInfo("Deleting: \(deletedIndexPaths.map({ String($0.row) }).joined(separator: ", "))")
DDLogInfo("Inserting: \(insertedIndexPaths.map({ String($0.row) }).joined(separator: ", "))")
delegate?.controllerWillChangeContent(self)
delegate?.controller(self, changedObjectsAt: changedIndexPaths)
delegate?.controller(self, removedObjectsAt: deletedIndexPaths)
delegate?.controller(self, addedObjectsAt: insertedIndexPaths)
delegate?.controllerDidChangeContent(self)
}
}
func setSections(_ sections: [Section], selection: Item, animated: Bool) {
// We don't support animating changes in the number/order of sections.
guard animated && sections.count == sections_storage.count else {
sections_storage = sections
tableView.reloadData()
self.selection = selection
if let indexPath = indexPath(for: selection) {
tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
}
scrollSelectionToVisible(animated: animated)
return
}
var deletedIndexPaths:[IndexPath] = []
var insertedIndexPaths:[IndexPath] = []
for (section, (old, new)) in zip(sections_storage, sections).enumerated() {
let changes = compare(original: old.items, modified: new.items)
var originalOffset = 0
var finalOffset = 0
for change in changes {
switch change.status {
case .deleted:
deletedIndexPaths += (0 ..< change.items.count).map { IndexPath(row: $0 + originalOffset, section: section) }
originalOffset += change.items.count
case .noChange:
originalOffset += change.items.count
finalOffset += change.items.count
case .inserted:
insertedIndexPaths += (0 ..< change.items.count).map { IndexPath(row: $0 + finalOffset, section: section) }
finalOffset += change.items.count
}
}
}
self.sections_storage = sections
self.selection = selection
if deletedIndexPaths.count > 0 || insertedIndexPaths.count > 0 {
CATransaction.begin()
CATransaction.setCompletionBlock({ [weak self] in
self?.scrollSelectionToVisible(animated: true)
})
tableView.beginUpdates()
tableView.deleteRows(at: deletedIndexPaths, with: .fade)
tableView.insertRows(at: insertedIndexPaths, with: .fade)
tableView.endUpdates()
CATransaction.commit()
} else {
scrollSelectionToVisible(animated: true)
}
reloadCells(in: 1)
if let indexPath = indexPath(for: selection) {
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
}
}
func rebuild(selection: Item, animated: Bool) {
setSections(build(), selection: selection, animated: animated)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment