From f713382e1bbf1b97599b13076273224231c60561 Mon Sep 17 00:00:00 2001 From: Alex Usbergo Date: Mon, 7 Oct 2019 21:26:16 +0200 Subject: [PATCH] format --- samples/BufferDemo/AppDelegate.swift | 2 +- samples/BufferDemo/Lorem.swift | 55 ++++- samples/BufferDemo/ViewController.swift | 14 +- src/AdapterType.swift | 4 + src/AnyListItem.swift | 61 +++--- src/Buffer.swift | 41 ++-- src/CollectionViewDiffAdapter.swift | 279 ++++++++++++----------- src/Diffing.swift | 81 ++++--- src/PrototypeCell.swift | 280 ++++++++++++------------ src/TableView.swift | 65 +++--- src/TableViewDiffAdapter.swift | 252 ++++++++++----------- 11 files changed, 627 insertions(+), 507 deletions(-) diff --git a/samples/BufferDemo/AppDelegate.swift b/samples/BufferDemo/AppDelegate.swift index c688cfc..a38bec8 100644 --- a/samples/BufferDemo/AppDelegate.swift +++ b/samples/BufferDemo/AppDelegate.swift @@ -4,6 +4,7 @@ import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { /// The key window. var window: UIWindow? + /// Tells the delegate that the launch process is almost done and the app is almost ready to run. func application( _ application: UIApplication, @@ -14,4 +15,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } - diff --git a/samples/BufferDemo/Lorem.swift b/samples/BufferDemo/Lorem.swift index e98c183..35c6af8 100644 --- a/samples/BufferDemo/Lorem.swift +++ b/samples/BufferDemo/Lorem.swift @@ -4,66 +4,86 @@ class Lorem { static var word: String { return allWords.randomElement } + static func words(_ count: Int) -> String { return compose({ word }, count: count, middleSeparator: .Space) } + static var sentence: String { let numberOfWordsInSentence = Int.random(min: 4, max: 16) let capitalizeFirstLetterDecorator: (String) -> String = { $0.stringWithCapitalizedFirstLetter } - return compose({ word }, count: numberOfWordsInSentence, middleSeparator: .Space, endSeparator: .Dot, decorator: capitalizeFirstLetterDecorator) + return compose( + { word }, count: numberOfWordsInSentence, middleSeparator: .Space, endSeparator: .Dot, + decorator: capitalizeFirstLetterDecorator) } + static func sentences(_ count: Int) -> String { return compose({ sentence }, count: count, middleSeparator: .Space) } + static var paragraph: String { let numberOfSentencesInParagraph = Int.random(min: 3, max: 9) return sentences(numberOfSentencesInParagraph) } + static func paragraphs(_ count: Int) -> String { return compose({ paragraph }, count: count, middleSeparator: .NewLine) } + static var title: String { let numberOfWordsInTitle = Int.random(min: 2, max: 7) let capitalizeStringDecorator: (String) -> String = { $0.capitalized } - return compose({ word }, count: numberOfWordsInTitle, middleSeparator: .Space, decorator: capitalizeStringDecorator) + return compose( + { word }, count: numberOfWordsInTitle, middleSeparator: .Space, + decorator: capitalizeStringDecorator) } + static var firstName: String { return firstNames.randomElement } + static var lastName: String { return lastNames.randomElement } + static var name: String { return "\(firstName) \(lastName)" } + static var email: String { let delimiter = emailDelimiters.randomElement let domain = emailDomains.randomElement return "\(firstName)\(delimiter)\(lastName)@\(domain)".lowercased() } + static var URL: Foundation.URL { return Foundation.URL(string: "http://\(domains.randomElement)/")! } + static var tweet: String { return tweets.randomElement } + static var date: Date { let currentDate = Date() let currentCalendar = Calendar.current var referenceDateComponents = DateComponents() referenceDateComponents.year = -4 let calendarOptions = NSCalendar.Options(rawValue: 0) - let referenceDate = (currentCalendar as NSCalendar).date(byAdding: referenceDateComponents, to: currentDate, options: calendarOptions)! + let referenceDate = (currentCalendar as NSCalendar).date( + byAdding: referenceDateComponents, to: currentDate, options: calendarOptions)! let timeIntervalSinceReferenceDate = currentDate.timeIntervalSince(referenceDate) let randomTimeInterval = TimeInterval(Int.random(max: Int(timeIntervalSinceReferenceDate))) return referenceDate.addingTimeInterval(randomTimeInterval) } + fileprivate enum Separator: String { case None = "" case Space = " " case Dot = "." case NewLine = "\n" } + fileprivate static func compose( _ provider: () -> String, count: Int, @@ -74,7 +94,7 @@ class Lorem { var composedString = "" for index in 0.. Bool { +func == (lhs: FooModel, rhs: FooModel) -> Bool { return lhs.text == rhs.text } struct FooModel: Diffable { /// The identifier used from the diffing algorithm. var diffIdentifier: String { return text } + /// Simple text property. let text: String } @@ -19,6 +20,7 @@ class ViewController: UIViewController, UITableViewDelegate { tableView.delegate = self return tableView }() + /// Some dummy elements. lazy var elements: [ListItem] = { var elements = [ListItem]() @@ -26,7 +28,8 @@ class ViewController: UIViewController, UITableViewDelegate { let item = ListItem( type: UITableViewCell.self, container: self.tableView, - model: FooModel(text: ("\(i)"))) { cell, model in cell.textLabel?.text = model.text } + model: FooModel(text: ("\(i)")) + ) { cell, model in cell.textLabel?.text = model.text } elements.append(item) } return elements @@ -53,8 +56,3 @@ class ViewController: UIViewController, UITableViewDelegate { tableView.elements = newElements } } - - - - - diff --git a/src/AdapterType.swift b/src/AdapterType.swift index 8140c1c..8e80e96 100644 --- a/src/AdapterType.swift +++ b/src/AdapterType.swift @@ -6,16 +6,20 @@ public protocol AdapterType { /// Returns the element currently on the front buffer at the given index path. func displayedElement(at index: Int) -> Type + /// The total number of elements currently displayed. func countDisplayedElements() -> Int + /// Replace the elements buffer and compute the diffs. /// - parameter newValues: The new values. /// - parameter synchronous: Wether the filter, sorting and diff should be executed /// synchronously or not. /// - parameter completion: Code that will be executed once the buffer is updated. func update(with values: [Type]?, synchronous: Bool, completion: (() -> Void)?) + /// The section index associated with this adapter. var sectionIndex: Int { get set } + /// The target view. var view: ViewType? { get } diff --git a/src/AnyListItem.swift b/src/AnyListItem.swift index 547e91b..aa95695 100644 --- a/src/AnyListItem.swift +++ b/src/AnyListItem.swift @@ -1,23 +1,26 @@ import Foundation -public protocol ListContainerView: class { } -public protocol ListViewCell: class { } +public protocol ListContainerView: class {} +public protocol ListViewCell: class {} #if os(iOS) import UIKit - extension UITableView: ListContainerView { } - extension UICollectionView: ListContainerView { } - extension UITableViewCell: ListViewCell { } - extension UICollectionViewCell: ListViewCell { } + extension UITableView: ListContainerView {} + extension UICollectionView: ListContainerView {} + extension UITableViewCell: ListViewCell {} + extension UICollectionViewCell: ListViewCell {} #endif public protocol ListItemType: Diffable { /// The reuse identifier for the cell passed as argument. var reuseIdentifier: String { get set } + /// The TableView, or the CollectionView that will own this element. var referenceView: ListContainerView? { get } + /// A opaque pointer to the model associated to this row. var modelRef: Any? { get } + /// Configure the cell with the current item. func configure(cell: ListViewCell) } @@ -25,40 +28,46 @@ public protocol ListItemType: Diffable { public class ListItem: ListItemType, CustomDebugStringConvertible { /// The cell reuse identifier. public var reuseIdentifier: String + /// The unique identifier (used for the diffing algorithm). public var diffIdentifier: String { return "\(reuseIdentifier)_\(model.diffIdentifier)" } + /// The *UICollectionView/UITableView* associated to this item. public weak var referenceView: ListContainerView? + /// The actual model object. public var model: T + /// A opaque pointer to the model associated to this row. public var modelRef: Any? { return self.model } + /// A textual representation of this instance, suitable for debugging. public var debugDescription: String { return self.diffIdentifier } + /// The configuration block applied to the target cell. public var cellConfiguration: ((ListViewCell, T) -> Void)? #if os(iOS) - public init( - type: V.Type, - container: ListContainerView, - reuseIdentifer: String = String(describing: V.self), - model: T, - configurationClosure: ((V, T) -> Void)? = nil - ) { - // Registers the prototype cell if necessary. - if !Prototypes.isPrototypeCellRegistered(reuseIdentifer) { - let cell = V(reuseIdentifier: reuseIdentifer) - Prototypes.registerPrototypeCell(reuseIdentifer, cell: cell) - } - self.reuseIdentifier = reuseIdentifer - if let closure = configurationClosure { - self.cellConfiguration = { cell, type in return closure(cell as! V, type) } + public init( + type: V.Type, + container: ListContainerView, + reuseIdentifer: String = String(describing: V.self), + model: T, + configurationClosure: ((V, T) -> Void)? = nil + ) { + // Registers the prototype cell if necessary. + if !Prototypes.isPrototypeCellRegistered(reuseIdentifer) { + let cell = V(reuseIdentifier: reuseIdentifer) + Prototypes.registerPrototypeCell(reuseIdentifer, cell: cell) + } + self.reuseIdentifier = reuseIdentifer + if let closure = configurationClosure { + self.cellConfiguration = { cell, type in return closure(cell as! V, type) } + } + self.referenceView = container + self.model = model + self.registerReferenceView(with: type) } - self.referenceView = container - self.model = model - self.registerReferenceView(with: type) - } #endif public init( @@ -91,6 +100,6 @@ public class ListItem: ListItemType, CustomDebugStringConvertible { /// Bind the cell passed as argument to this data item. public func configure(cell: ListViewCell) { - self.cellConfiguration?(cell, self.model) + self.cellConfiguration?(cell, self.model) } } diff --git a/src/Buffer.swift b/src/Buffer.swift index d5024a9..36f4697 100644 --- a/src/Buffer.swift +++ b/src/Buffer.swift @@ -1,18 +1,23 @@ import Foundation -public protocol BufferType { } +public protocol BufferType {} public protocol BufferDelegate: class { /// Notifies the receiver that the content is about to change. func buffer(willChangeContent buffer: BufferType) + /// Notifies the receiver that rows were deleted. func buffer(didDeleteElementAtIndices buffer: BufferType, indices: [UInt]) + /// Notifies the receiver that rows were inserted. func buffer(didInsertElementsAtIndices buffer: BufferType, indices: [UInt]) + /// Notifies the receiver that an element has been moved to a different position. func buffer(didMoveElement buffer: BufferType, from: UInt, to: UInt) + /// Notifies the receiver that the content updates has ended. func buffer(didChangeContent buffer: BufferType) + /// Notifies the receiver that the content updates has ended. /// This callback method is called when the number of changes are too many to be /// handled for the UI thread - it's recommendable to just reload the whole data in this case. @@ -20,6 +25,7 @@ public protocol BufferDelegate: class { /// of changes /// that you want the receiver to be notified for. func buffer(didChangeAllContent buffer: BufferType) + /// Called when one of the observed properties for this object changed. func buffer(didChangeElementAtIndex buffer: BufferType, index: UInt) } @@ -27,34 +33,44 @@ public protocol BufferDelegate: class { public class Buffer: NSObject, BufferType { /// The object that will get notified every time chavbcnges occures to the array. public weak var delegate: BufferDelegate? + /// The elements in the array observer's buffer. public var currentElements: [E] { return self.frontBuffer } + /// Defines what is the maximum number of changes that you want the receiver to be notified for. /// - note: If the number of changes exceeds this number, *buffer(didChangeAllContent:)* is going /// to be invoked instead. public var diffThreshold = 100 + /// If set to 'true' the LCS algorithm is run synchronously on the main thread. private var synchronous: Bool = false + /// The exposed array. private var frontBuffer = [E]() + /// The internal array. private var backBuffer = [E]() + /// Sort closure. private var sort: ((E, E) -> Bool)? + /// Filter closure. private var filter: ((E) -> Bool)? + /// The serial operation queue for this controller. private let serialOperationQueue: OperationQueue = { let operationQueue = OperationQueue() operationQueue.maxConcurrentOperationCount = 1 return operationQueue }() + /// Internal state flags. private var flags = ( isRefreshing: false, - shouldRefresh: false) + shouldRefresh: false + ) /// Constructs a new buffer instance. public init( @@ -72,7 +88,7 @@ public class Buffer: NSObject, BufferType { with values: [E]? = nil, synchronous: Bool = false, completion: (() -> Void)? = nil - ) -> Void { + ) { // Should be called on the main thread. assert(Thread.isMainThread) let new = values ?? self.frontBuffer @@ -97,8 +113,9 @@ public class Buffer: NSObject, BufferType { } // Compute the diffing. let diff = Diff.diffing( - oldArray: self.frontBuffer.map { $0.diffIdentifier}, - newArray: backBuffer.map { $0.diffIdentifier }) { + oldArray: self.frontBuffer.map { $0.diffIdentifier }, + newArray: backBuffer.map { $0.diffIdentifier } + ) { return $0 == $1 } // Update the front buffer on the main thread. @@ -137,14 +154,14 @@ public class Buffer: NSObject, BufferType { //MARK: Dispatch Helpers -private extension Buffer { +extension Buffer { /// Dispatches the block passed as argument on the main thread. /// - parameter synchronous: If true the block is going to be called on the current call stack. /// - parameter block: The block that is going to be executed. - func dispatchOnMainThread( + fileprivate func dispatchOnMainThread( synchronous: Bool = false, block: @escaping () -> Void - ) -> Void { + ) { if synchronous { assert(Thread.isMainThread) block() @@ -156,13 +173,14 @@ private extension Buffer { DispatchQueue.main.async(execute: block) } } + /// Dispatches the block passed as argument on the ad-hoc serial queue (if necesary). /// - parameter synchronous: If true the block is going to be called on the current call stack. /// - parameter block: The block that is going to be executed. - func dispatchOnSerialQueue( + fileprivate func dispatchOnSerialQueue( synchronous: Bool = false, block: @escaping () -> Void - ) -> Void { + ) { if synchronous { block() return @@ -172,6 +190,3 @@ private extension Buffer { } } } - - - diff --git a/src/CollectionViewDiffAdapter.swift b/src/CollectionViewDiffAdapter.swift index 016ffc7..f0a4b12 100644 --- a/src/CollectionViewDiffAdapter.swift +++ b/src/CollectionViewDiffAdapter.swift @@ -1,150 +1,167 @@ #if os(iOS) -import UIKit - -open class CollectionViewDiffAdapter: - NSObject, - AdapterType, - UICollectionViewDataSource { - - public typealias `Type` = E - public typealias ViewType = UICollectionView - /// The buffer object. - open fileprivate(set) var buffer: Buffer - /// The collection view owning this adapter. - open fileprivate(set) weak var view: ViewType? - /// Right now this only works on a single section of a collectionView. - /// If your collectionView has multiple sections, though, you can just use multiple - /// CollectionViewDiffAdapter, one per section, and set this value appropriately on each one. - open var sectionIndex: Int = 0 - /// The indexpaths used for the batch update. - fileprivate var indexPaths: ( - insertion: [IndexPath], - deletion: [IndexPath], - move: [(IndexPath, IndexPath)] ) = ([], [], []) - /// The *cellForItemAtIndexPath* block. - fileprivate var cellForItemAtIndexPath: - ((UICollectionView, E, IndexPath) -> UICollectionViewCell)? = nil - - public required init(buffer: BufferType, view: ViewType) { - guard let buffer = buffer as? Buffer else { - fatalError() + import UIKit + + open class CollectionViewDiffAdapter: + NSObject, + AdapterType, + UICollectionViewDataSource + { + + public typealias `Type` = E + public typealias ViewType = UICollectionView + + /// The buffer object. + open fileprivate(set) var buffer: Buffer + + /// The collection view owning this adapter. + open fileprivate(set) weak var view: ViewType? + + /// Right now this only works on a single section of a collectionView. + /// If your collectionView has multiple sections, though, you can just use multiple + /// CollectionViewDiffAdapter, one per section, and set this value appropriately on each one. + open var sectionIndex: Int = 0 + + /// The indexpaths used for the batch update. + fileprivate var indexPaths: + ( + insertion: [IndexPath], + deletion: [IndexPath], + move: [(IndexPath, IndexPath)] + ) + = ([], [], []) + + /// The *cellForItemAtIndexPath* block. + fileprivate var cellForItemAtIndexPath: + ((UICollectionView, E, IndexPath) -> UICollectionViewCell)? + = nil + + public required init(buffer: BufferType, view: ViewType) { + guard let buffer = buffer as? Buffer else { + fatalError() + } + self.buffer = buffer + self.view = view + super.init() + self.buffer.delegate = self } - self.buffer = buffer - self.view = view - super.init() - self.buffer.delegate = self - } - public required init(initialElements: [E], view: ViewType) { - self.buffer = Buffer(initialArray: initialElements) - self.view = view - super.init() - self.buffer.delegate = self - } + public required init(initialElements: [E], view: ViewType) { + self.buffer = Buffer(initialArray: initialElements) + self.view = view + super.init() + self.buffer.delegate = self + } - /// Returns the element currently on the front buffer at the given index path. - open func displayedElement(at index: Int) -> E { - return self.buffer.currentElements[index] - } + /// Returns the element currently on the front buffer at the given index path. + open func displayedElement(at index: Int) -> E { + return self.buffer.currentElements[index] + } - /// The total number of elements currently displayed. - open func countDisplayedElements() -> Int { - return self.buffer.currentElements.count - } + /// The total number of elements currently displayed. + open func countDisplayedElements() -> Int { + return self.buffer.currentElements.count + } - /// Replace the elements buffer and compute the diffs. - /// - parameter newValues: The new values. - /// - parameter synchronous: Wether the filter, sorting and diff should be executed - /// synchronously or not. - /// - parameter completion: Code that will be executed once the buffer is updated. - open func update( - with newValues: [E]? = nil, - synchronous: Bool = false, - completion: (() -> Void)? = nil - ) -> Void { - self.buffer.update(with: newValues, synchronous: synchronous, completion: completion) - } + /// Replace the elements buffer and compute the diffs. + /// - parameter newValues: The new values. + /// - parameter synchronous: Wether the filter, sorting and diff should be executed + /// synchronously or not. + /// - parameter completion: Code that will be executed once the buffer is updated. + open func update( + with newValues: [E]? = nil, + synchronous: Bool = false, + completion: (() -> Void)? = nil + ) { + self.buffer.update(with: newValues, synchronous: synchronous, completion: completion) + } - /// Configure the TableView to use this adapter as its DataSource. - /// - parameter automaticDimension: If you wish to use 'UITableViewAutomaticDimension' - /// as 'rowHeight'. - /// - parameter estimatedHeight: The estimated average height for the cells. - /// - parameter cellForRowAtIndexPath: The closure that returns a cell for the given - /// index path. - open func useAsDataSource(_ cellForItemAtIndexPath: - @escaping (UICollectionView, E, IndexPath) -> UICollectionViewCell) { - self.view?.dataSource = self - self.cellForItemAtIndexPath = cellForItemAtIndexPath - } + /// Configure the TableView to use this adapter as its DataSource. + /// - parameter automaticDimension: If you wish to use 'UITableViewAutomaticDimension' + /// as 'rowHeight'. + /// - parameter estimatedHeight: The estimated average height for the cells. + /// - parameter cellForRowAtIndexPath: The closure that returns a cell for the given + /// index path. + open func useAsDataSource( + _ cellForItemAtIndexPath: + @escaping (UICollectionView, E, IndexPath) -> UICollectionViewCell + ) { + self.view?.dataSource = self + self.cellForItemAtIndexPath = cellForItemAtIndexPath + } - /// Tells the data source to return the number of rows in a given section of a table view. - open func collectionView( - _ collectionView: UICollectionView, - numberOfItemsInSection section: Int - ) -> Int { - return self.buffer.currentElements.count - } + /// Tells the data source to return the number of rows in a given section of a table view. + open func collectionView( + _ collectionView: UICollectionView, + numberOfItemsInSection section: Int + ) -> Int { + return self.buffer.currentElements.count + } - /// Asks the data source for a cell to insert in a particular location of the table view. - open func collectionView( - _ collectionView: UICollectionView, - cellForItemAt indexPath: IndexPath - ) -> UICollectionViewCell { - return self.cellForItemAtIndexPath!( - collectionView, - buffer.currentElements[(indexPath as NSIndexPath).row], - indexPath) + /// Asks the data source for a cell to insert in a particular location of the table view. + open func collectionView( + _ collectionView: UICollectionView, + cellForItemAt indexPath: IndexPath + ) -> UICollectionViewCell { + return self.cellForItemAtIndexPath!( + collectionView, + buffer.currentElements[(indexPath as NSIndexPath).row], + indexPath) + } } -} -extension CollectionViewDiffAdapter: BufferDelegate { - /// Notifies the receiver that the content is about to change. - public func buffer(willChangeContent buffer: BufferType) { - self.indexPaths = ([], [], []) - } + extension CollectionViewDiffAdapter: BufferDelegate { + /// Notifies the receiver that the content is about to change. + public func buffer(willChangeContent buffer: BufferType) { + self.indexPaths = ([], [], []) + } - /// Notifies the receiver that rows were deleted. - public func buffer(didDeleteElementAtIndices buffer: BufferType, indices: [UInt]) { - self.indexPaths.deletion = indices.map({ - IndexPath(row: Int($0), section: self.sectionIndex) - }) - } + /// Notifies the receiver that rows were deleted. + public func buffer(didDeleteElementAtIndices buffer: BufferType, indices: [UInt]) { + self.indexPaths.deletion + = indices.map({ + IndexPath(row: Int($0), section: self.sectionIndex) + }) + } - /// Notifies the receiver that rows were inserted. - public func buffer(didInsertElementsAtIndices buffer: BufferType, indices: [UInt]) { - self.indexPaths.insertion = indices.map({ - IndexPath(row: Int($0), section: self.sectionIndex) - }) - } - - /// Notifies the receiver that a row got moved. - public func buffer(didMoveElement buffer: BufferType, from: UInt, to: UInt) { - self.indexPaths.move.append(( - IndexPath(row: Int(from), section: self.sectionIndex), - IndexPath(row: Int(to), section: self.sectionIndex))) - } + /// Notifies the receiver that rows were inserted. + public func buffer(didInsertElementsAtIndices buffer: BufferType, indices: [UInt]) { + self.indexPaths.insertion + = indices.map({ + IndexPath(row: Int($0), section: self.sectionIndex) + }) + } - /// Notifies the receiver that the content updates has ended. - public func buffer(didChangeContent buffer: BufferType) { - self.view?.performBatchUpdates({ - self.view?.insertItems(at: self.indexPaths.insertion) - self.view?.deleteItems(at: self.indexPaths.deletion) - for move in self.indexPaths.move { - self.view?.moveItem(at: move.0, to: move.1) - } - }, completion: nil) - } + /// Notifies the receiver that a row got moved. + public func buffer(didMoveElement buffer: BufferType, from: UInt, to: UInt) { + self.indexPaths.move.append( + ( + IndexPath(row: Int(from), section: self.sectionIndex), + IndexPath(row: Int(to), section: self.sectionIndex) + )) + } - /// Called when one of the observed properties for this object changed. - public func buffer(didChangeElementAtIndex buffer: BufferType, index: UInt) { - self.view?.reloadItems( - at: [IndexPath(row: Int(index), section: self.sectionIndex)]) - } + /// Notifies the receiver that the content updates has ended. + public func buffer(didChangeContent buffer: BufferType) { + self.view?.performBatchUpdates( + { + self.view?.insertItems(at: self.indexPaths.insertion) + self.view?.deleteItems(at: self.indexPaths.deletion) + for move in self.indexPaths.move { + self.view?.moveItem(at: move.0, to: move.1) + } + }, completion: nil) + } - /// Notifies the receiver that the content updates has ended and the whole array changed. - public func buffer(didChangeAllContent buffer: BufferType) { - self.view?.reloadData() + /// Called when one of the observed properties for this object changed. + public func buffer(didChangeElementAtIndex buffer: BufferType, index: UInt) { + self.view?.reloadItems( + at: [IndexPath(row: Int(index), section: self.sectionIndex)]) + } + + /// Notifies the receiver that the content updates has ended and the whole array changed. + public func buffer(didChangeAllContent buffer: BufferType) { + self.view?.reloadData() + } } -} #endif diff --git a/src/Diffing.swift b/src/Diffing.swift index ceffc59..5b52e34 100644 --- a/src/Diffing.swift +++ b/src/Diffing.swift @@ -16,16 +16,20 @@ public protocol Diffing { static func diffing( oldArray: [T], newArray: [T], - isEqual: EqualityChecker) -> DiffResultType + isEqual: EqualityChecker + ) -> DiffResultType } public protocol DiffResultType { /// The computed required insert to match the destination array. var inserts: IndexSet { get set } + /// The computed required updates to match the destination array. var updates: IndexSet { get set } + /// The computed required deletes to match the destination array. var deletes: IndexSet { get set } + /// The computed required moves to match the destination array. var moves: [MoveIndex] { get set } } @@ -33,6 +37,7 @@ public protocol DiffResultType { public struct MoveIndex: Equatable { /// The source index. public let from: Int + /// The target index. public let to: Int @@ -40,6 +45,7 @@ public struct MoveIndex: Equatable { self.from = from self.to = to } + /// Returns *true* only if the two indices are the same. public static func == (lhs: MoveIndex, rhs: MoveIndex) -> Bool { return lhs.from == rhs.from && lhs.to == rhs.to @@ -49,15 +55,20 @@ public struct MoveIndex: Equatable { public struct DiffResult: DiffResultType { /// The computed required insert to match the destination array. public var inserts = IndexSet() + /// The computed required updates to match the destination array. public var updates = IndexSet() + /// The computed required deletes to match the destination array. public var deletes = IndexSet() + /// The computed required moves to match the destination array. - public var moves = Array() + public var moves = [MoveIndex]() + /// Internal data. - public var oldMap = Dictionary() - public var newMap = Dictionary() + public var oldMap = [H: Int]() + + public var newMap = [H: Int]() public func validate(_ oldArray: [T], _ newArray: [T]) -> Bool { return (oldArray.count + self.inserts.count - self.deletes.count) == newArray.count @@ -77,6 +88,7 @@ extension DiffResultType { public var hasChanges: Bool { return inserts.isEmpty && deletes.isEmpty && updates.isEmpty && moves.isEmpty } + /// The number of changes. public var changeCount: Int { return inserts.count + deletes.count + updates.count + moves.count @@ -89,52 +101,64 @@ public enum Diff: Diffing { private struct Stack { /// All of the items in the stack. var items = [T]() + /// Whether there are any elements in this stack. var isEmpty: Bool { return self.items.isEmpty } + /// Adds a new elements to the stack. mutating func push(_ item: T) { items.append(item) } + /// Remove the most recently added element to the stack. mutating func pop() -> T { return items.removeLast() } } + /// Used to track data stats while diffing. /// We expect to keep a reference of entry, thus its declaration as (final) class. private final class Entry { /// The number of times the data occurs in the old array var oldCounter: Int = 0 + /// The number of times the data occurs in the new array var newCounter: Int = 0 + /// The indexes of the data in the old array var oldIndexes: Stack = Stack() + /// Flag marking if the data has been updated between arrays by equality check var updated: Bool = false + /// Returns `true` if the data occur on both sides, `false` otherwise var occurOnBothSides: Bool { return self.newCounter > 0 && self.oldCounter > 0 } + func push(new index: Int?) { self.newCounter += 1 self.oldIndexes.push(index) } + func push(old index: Int?) { - self.oldCounter += 1; + self.oldCounter += 1 self.oldIndexes.push(index) } } + /// Track both the entry and the algorithm index. Default the index to `nil` private struct Record { let entry: Entry var index: Int? + init(_ entry: Entry) { self.entry = entry self.index = nil } } - private struct OptimizedIdentity : Hashable { + private struct OptimizedIdentity: Hashable { let hashValue: Int let identity: UnsafePointer @@ -157,7 +181,7 @@ public enum Diff: Diffing { isEqual: EqualityChecker ) -> DiffResultType { // symbol table uses the old/new array `diffIdentifier` as the key and `Entry` as the value - var table = Dictionary, Entry>.init(minimumCapacity: oldArray.count) + var table = [OptimizedIdentity: Entry].init(minimumCapacity: oldArray.count) let __oldArray = ContiguousArray(oldArray) let __newArray = ContiguousArray(newArray) // pass 1 @@ -238,26 +262,27 @@ public enum Diff: Diffing { } //reset and track offsets from inserted items to calculate where items have moved runningOffset = 0 - _ = newRecords.enumerated().map { (i, newRecord) -> Int in - let insertOffset = runningOffset - if let oldIndex = newRecord.index { - // note that an entry can be updated /and/ moved - if newRecord.entry.updated { - result.updates.insert(oldIndex) + _ + = newRecords.enumerated().map { (i, newRecord) -> Int in + let insertOffset = runningOffset + if let oldIndex = newRecord.index { + // note that an entry can be updated /and/ moved + if newRecord.entry.updated { + result.updates.insert(oldIndex) + } + // calculate the offset and determine if there was a move + // if the indexes match, ignore the index + let deleteOffset = deleteOffsets[oldIndex] + if (oldIndex - deleteOffset + insertOffset) != i { + result.moves.append(MoveIndex(from: oldIndex, to: i)) + } + } else { // add to inserts if the opposing index is nil + result.inserts.insert(i) + runningOffset += 1 } - // calculate the offset and determine if there was a move - // if the indexes match, ignore the index - let deleteOffset = deleteOffsets[oldIndex] - if (oldIndex - deleteOffset + insertOffset) != i { - result.moves.append(MoveIndex(from: oldIndex, to: i)) - } - } else { // add to inserts if the opposing index is nil - result.inserts.insert(i) - runningOffset += 1 + result.newMap[__newArray[i].diffIdentifier] = i + return insertOffset } - result.newMap[__newArray[i].diffIdentifier] = i - return insertOffset - } assert(result.validate(oldArray, newArray)) return result } @@ -280,7 +305,5 @@ extension NSString: Diffable { extension FloatingPoint { public var diffIdentifier: String { return "\(self)" } } -extension Float : Diffable { } -extension Double : Diffable { } - - +extension Float: Diffable {} +extension Double: Diffable {} diff --git a/src/PrototypeCell.swift b/src/PrototypeCell.swift index a501071..83c5839 100644 --- a/src/PrototypeCell.swift +++ b/src/PrototypeCell.swift @@ -1,159 +1,169 @@ #if os(iOS) -import UIKit - -public protocol PrototypeViewCell: ListViewCell { - /// The TableView or CollectionView that owns this cell. - var referenceView: ListContainerView? { get set } - /// Apply the model. - /// - Note: This is used internally from the infra to set the model. - func applyModel(_ model: Any?) - /// Constructor. - init(reuseIdentifier: String) -} - -open class PrototypeTableViewCell : UITableViewCell, PrototypeViewCell { - /// The wrapped view. - open var view: UIView! - /// The associated model. - open var model: Any? { - didSet { didSetModelClosure?(model) } - } - /// The *UICollectionView/UITableView* for this prototype cell. - open weak var referenceView: ListContainerView? - /// Internal state. - fileprivate var didInitializeCell = false - fileprivate var didSetModelClosure: ((Any?) -> Void)? - - public required init(reuseIdentifier: String) { - super.init(style: .default, reuseIdentifier: reuseIdentifier) + import UIKit + + public protocol PrototypeViewCell: ListViewCell { + /// The TableView or CollectionView that owns this cell. + var referenceView: ListContainerView? { get set } + + /// Apply the model. + /// - Note: This is used internally from the infra to set the model. + func applyModel(_ model: Any?) + + /// Constructor. + init(reuseIdentifier: String) } - required public init?(coder aDecoder: NSCoder) { + open class PrototypeTableViewCell: UITableViewCell, PrototypeViewCell { + /// The wrapped view. + open var view: UIView! + + /// The associated model. + open var model: Any? { + didSet { didSetModelClosure?(model) } + } + + /// The *UICollectionView/UITableView* for this prototype cell. + open weak var referenceView: ListContainerView? + + /// Internal state. + fileprivate var didInitializeCell = false + + fileprivate var didSetModelClosure: ((Any?) -> Void)? + + public required init(reuseIdentifier: String) { + super.init(style: .default, reuseIdentifier: reuseIdentifier) + } + + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") - } + } - open func initializeCellIfNecessary( - _ view: UIView, - didSetModel: ((Any?) -> Void)? = nil - ) -> Void { - assert(Thread.isMainThread) - // make sure this happens just once. - if self.didInitializeCell { return } - self.didInitializeCell = true - - self.view = view - self.contentView.addSubview(view) - self.didSetModelClosure = didSetModel - } + open func initializeCellIfNecessary( + _ view: UIView, + didSetModel: ((Any?) -> Void)? = nil + ) { + assert(Thread.isMainThread) + // make sure this happens just once. + if self.didInitializeCell { return } + self.didInitializeCell = true + + self.view = view + self.contentView.addSubview(view) + self.didSetModelClosure = didSetModel + } - open func applyModel(_ model: Any?) { - guard let model = model else { return } - self.model = model - } + open func applyModel(_ model: Any?) { + guard let model = model else { return } + self.model = model + } - /// Asks the view to calculate and return the size that best fits the specified size. - /// - parameter size: The size for which the view should calculate its best-fitting size. - /// - returns: A new size that fits the receiver’s subviews. - open override func sizeThatFits(_ size: CGSize) -> CGSize { - let size = view.sizeThatFits(size) - return size - } + /// Asks the view to calculate and return the size that best fits the specified size. + /// - parameter size: The size for which the view should calculate its best-fitting size. + /// - returns: A new size that fits the receiver’s subviews. + open override func sizeThatFits(_ size: CGSize) -> CGSize { + let size = view.sizeThatFits(size) + return size + } - /// Returns the natural size for the receiving view, considering only properties of the - /// view itself. - /// - returns: A size indicating the natural size for the receiving view based on its - /// intrinsic properties. - open override var intrinsicContentSize : CGSize { - return view.intrinsicContentSize - } -} - -open class PrototypeCollectionViewCell: UICollectionViewCell, PrototypeViewCell { - ///The wrapped view. - open var view: UIView! - ///The model for this cell. - /// - Note: This is propagated to the associted. - open var model: Any? { - didSet { didSetModelClosure?(model) } - } - /// The *UICollectionView/UITableView* for this prototype cell. - weak open var referenceView: ListContainerView? - /// Internal state. - fileprivate var didInitializeCell = false - fileprivate var didSetModelClosure: ((Any?) -> Void)? - - public required init(reuseIdentifier: String) { - super.init(frame: CGRect.zero) + /// Returns the natural size for the receiving view, considering only properties of the + /// view itself. + /// - returns: A size indicating the natural size for the receiving view based on its + /// intrinsic properties. + open override var intrinsicContentSize: CGSize { + return view.intrinsicContentSize + } } - required public init?(coder aDecoder: NSCoder) { + open class PrototypeCollectionViewCell: UICollectionViewCell, PrototypeViewCell { + ///The wrapped view. + open var view: UIView! + + ///The model for this cell. + /// - Note: This is propagated to the associted. + open var model: Any? { + didSet { didSetModelClosure?(model) } + } + + /// The *UICollectionView/UITableView* for this prototype cell. + weak open var referenceView: ListContainerView? + + /// Internal state. + fileprivate var didInitializeCell = false + + fileprivate var didSetModelClosure: ((Any?) -> Void)? + + public required init(reuseIdentifier: String) { + super.init(frame: CGRect.zero) + } + + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") - } + } - open func initializeCellIfNecessary( - _ view: UIView, - didSetModel: ((Any?) -> Void)? = nil - ) -> Void { - assert(Thread.isMainThread) - // make sure this happens just once - if self.didInitializeCell { return } - self.didInitializeCell = true - self.view = view - self.contentView.addSubview(view) - self.didSetModelClosure = didSetModel - } + open func initializeCellIfNecessary( + _ view: UIView, + didSetModel: ((Any?) -> Void)? = nil + ) { + assert(Thread.isMainThread) + // make sure this happens just once + if self.didInitializeCell { return } + self.didInitializeCell = true + self.view = view + self.contentView.addSubview(view) + self.didSetModelClosure = didSetModel + } - open func applyModel(_ model: Any?) { - self.model = model - } + open func applyModel(_ model: Any?) { + self.model = model + } - /// Asks the view to calculate and return the size that best fits the specified size. - /// - parameter size: The size for which the view should calculate its best-fitting size. - /// - returns: A new size that fits the receiver’s subviews. - open override func sizeThatFits(_ size: CGSize) -> CGSize { - let size = view.sizeThatFits(size) - return size - } + /// Asks the view to calculate and return the size that best fits the specified size. + /// - parameter size: The size for which the view should calculate its best-fitting size. + /// - returns: A new size that fits the receiver’s subviews. + open override func sizeThatFits(_ size: CGSize) -> CGSize { + let size = view.sizeThatFits(size) + return size + } - /// Returns the natural size for the receiving view, considering only properties of the - /// view itself. - /// - returns: A size indicating the natural size for the receiving view based on its - /// intrinsic properties. - open override var intrinsicContentSize : CGSize { - return view.intrinsicContentSize + /// Returns the natural size for the receiving view, considering only properties of the + /// view itself. + /// - returns: A size indicating the natural size for the receiving view based on its + /// intrinsic properties. + open override var intrinsicContentSize: CGSize { + return view.intrinsicContentSize + } } -} -public struct Prototypes { - fileprivate static var registeredPrototypes = [String: PrototypeViewCell]() + public enum Prototypes { + fileprivate static var registeredPrototypes = [String: PrototypeViewCell]() - /// Wether there's a prototype registered for a given reusedIdentifier. - public static func isPrototypeCellRegistered(_ reuseIdentifier: String) -> Bool { - guard let _ = Prototypes.registeredPrototypes[reuseIdentifier] else { return false } - return true - } + /// Wether there's a prototype registered for a given reusedIdentifier. + public static func isPrototypeCellRegistered(_ reuseIdentifier: String) -> Bool { + guard let _ = Prototypes.registeredPrototypes[reuseIdentifier] else { return false } + return true + } - /// Register a cell a prototype for a given reuse identifer. - public static func registerPrototypeCell(_ reuseIdentifier: String, cell: PrototypeViewCell){ - Prototypes.registeredPrototypes[reuseIdentifier] = cell - } + /// Register a cell a prototype for a given reuse identifer. + public static func registerPrototypeCell(_ reuseIdentifier: String, cell: PrototypeViewCell) { + Prototypes.registeredPrototypes[reuseIdentifier] = cell + } - /// Computes the size for the cell registered as prototype associate to the item passed - /// as argument. - /// - parameter item: The target item for size calculation. - public static func prototypeCellSize(_ item: ListItemType) -> CGSize { - guard let cell = Prototypes.registeredPrototypes[item.reuseIdentifier] else { - fatalError("Unregistered prototype with reuse identifier \(item.reuseIdentifier).") - } - cell.applyModel(item.modelRef) - cell.referenceView = item.referenceView - item.configure(cell: cell) - if let cell = cell as? UIView { - return cell.bounds.size - } else { - return CGSize.zero + /// Computes the size for the cell registered as prototype associate to the item passed + /// as argument. + /// - parameter item: The target item for size calculation. + public static func prototypeCellSize(_ item: ListItemType) -> CGSize { + guard let cell = Prototypes.registeredPrototypes[item.reuseIdentifier] else { + fatalError("Unregistered prototype with reuse identifier \(item.reuseIdentifier).") + } + cell.applyModel(item.modelRef) + cell.referenceView = item.referenceView + item.configure(cell: cell) + if let cell = cell as? UIView { + return cell.bounds.size + } else { + return CGSize.zero + } } } -} #endif diff --git a/src/TableView.swift b/src/TableView.swift index 8d07c1f..9b8e8a7 100644 --- a/src/TableView.swift +++ b/src/TableView.swift @@ -1,43 +1,44 @@ #if os(iOS) -import UIKit + import UIKit -open class BufferTableView: UITableView { - /// The elements for the table view. - open var elements = [ListItem]() { - didSet { self.adapter.buffer.update(with: self.elements) } - } - /// The adapter for this table view. - open lazy var adapter: TableViewDiffAdapter> = { - return TableViewDiffAdapter(initialElements: [ListItem](), view: self) - }() + open class BufferTableView: UITableView { + /// The elements for the table view. + open var elements = [ListItem]() { + didSet { self.adapter.buffer.update(with: self.elements) } + } - /// Convenience constructor. - public convenience init() { - self.init(frame: CGRect.zero, style: .plain) - } + /// The adapter for this table view. + open lazy var adapter: TableViewDiffAdapter> = { + return TableViewDiffAdapter(initialElements: [ListItem](), view: self) + }() - /// Initializes and returns a table view object having the given frame and style. - public override init(frame: CGRect, style: UITableView.Style) { - super.init(frame: frame, style: style) - self.rowHeight = UITableView.automaticDimension - if #available(iOS 11, *) { - self.estimatedRowHeight = -1 - } else { - self.estimatedRowHeight = 64 + /// Convenience constructor. + public convenience init() { + self.init(frame: CGRect.zero, style: .plain) } - self.adapter.useAsDataSource() { tableView, item, indexPath in - let cell = tableView.dequeueReusableCell( - withIdentifier: item.reuseIdentifier, - for: indexPath as IndexPath) - item.cellConfiguration?(cell, item.model) - return cell + + /// Initializes and returns a table view object having the given frame and style. + public override init(frame: CGRect, style: UITableView.Style) { + super.init(frame: frame, style: style) + self.rowHeight = UITableView.automaticDimension + if #available(iOS 11, *) { + self.estimatedRowHeight = -1 + } else { + self.estimatedRowHeight = 64 + } + self.adapter.useAsDataSource { tableView, item, indexPath in + let cell = tableView.dequeueReusableCell( + withIdentifier: item.reuseIdentifier, + for: indexPath as IndexPath) + item.cellConfiguration?(cell, item.model) + return cell + } } - } - /// - note: Not supported. - required public init?(coder aDecoder: NSCoder) { + /// - note: Not supported. + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") + } } -} #endif diff --git a/src/TableViewDiffAdapter.swift b/src/TableViewDiffAdapter.swift index 81d4e62..8b4a51a 100644 --- a/src/TableViewDiffAdapter.swift +++ b/src/TableViewDiffAdapter.swift @@ -1,142 +1,150 @@ #if os(iOS) -import UIKit - -open class TableViewDiffAdapter: - NSObject, - AdapterType, - UITableViewDataSource { - - public typealias `Type` = E - public typealias ViewType = UITableView - /// The buffer object. - open fileprivate(set) var buffer: Buffer - /// The collection view owning this adapter. - open fileprivate(set) weak var view: ViewType? - /// Right now this only works on a single section of a tableView. - /// If your tableView has multiple sections, though, you can just use multiple - /// TableViewDiffAdapter, one per section, and set this value appropriately on each one. - open var sectionIndex: Int = 0 - /// The *cellForItemAtIndexPath* block. - fileprivate var cellForRowAtIndexPath: - ((UITableView, E, IndexPath) -> UITableViewCell)? = nil - - public required init(buffer: BufferType, view: ViewType) { - guard let buffer = buffer as? Buffer else { - fatalError() + import UIKit + + open class TableViewDiffAdapter: + NSObject, + AdapterType, + UITableViewDataSource + { + + public typealias `Type` = E + public typealias ViewType = UITableView + + /// The buffer object. + open fileprivate(set) var buffer: Buffer + + /// The collection view owning this adapter. + open fileprivate(set) weak var view: ViewType? + + /// Right now this only works on a single section of a tableView. + /// If your tableView has multiple sections, though, you can just use multiple + /// TableViewDiffAdapter, one per section, and set this value appropriately on each one. + open var sectionIndex: Int = 0 + + /// The *cellForItemAtIndexPath* block. + fileprivate var cellForRowAtIndexPath: + ((UITableView, E, IndexPath) -> UITableViewCell)? + = nil + + public required init(buffer: BufferType, view: ViewType) { + guard let buffer = buffer as? Buffer else { + fatalError() + } + self.buffer = buffer + self.view = view + super.init() + self.buffer.delegate = self } - self.buffer = buffer - self.view = view - super.init() - self.buffer.delegate = self - } - public required init(initialElements: [E], view: ViewType) { - self.buffer = Buffer(initialArray: initialElements) - self.view = view - super.init() - self.buffer.delegate = self - } + public required init(initialElements: [E], view: ViewType) { + self.buffer = Buffer(initialArray: initialElements) + self.view = view + super.init() + self.buffer.delegate = self + } - /// Returns the element currently on the front buffer at the given index path. - open func displayedElement(at index: Int) -> Type { - return self.buffer.currentElements[index] - } + /// Returns the element currently on the front buffer at the given index path. + open func displayedElement(at index: Int) -> Type { + return self.buffer.currentElements[index] + } - /// The total number of elements currently displayed. - open func countDisplayedElements() -> Int { - return self.buffer.currentElements.count - } + /// The total number of elements currently displayed. + open func countDisplayedElements() -> Int { + return self.buffer.currentElements.count + } - /// Replace the elements buffer and compute the diffs. - /// - parameter newValues: The new values. - /// - parameter synchronous: Wether the filter, sorting and diff should be - /// executed synchronously or not. - /// - parameter completion: Code that will be executed once the buffer is updated. - open func update( - with newValues: [E]? = nil, - synchronous: Bool = false, - completion: (() -> Void)? = nil - ) -> Void { - self.buffer.update(with: newValues, synchronous: synchronous, completion: completion) - } + /// Replace the elements buffer and compute the diffs. + /// - parameter newValues: The new values. + /// - parameter synchronous: Wether the filter, sorting and diff should be + /// executed synchronously or not. + /// - parameter completion: Code that will be executed once the buffer is updated. + open func update( + with newValues: [E]? = nil, + synchronous: Bool = false, + completion: (() -> Void)? = nil + ) { + self.buffer.update(with: newValues, synchronous: synchronous, completion: completion) + } - /// Configure the TableView to use this adapter as its DataSource. - /// - parameter automaticDimension: If you wish to use 'UITableViewAutomaticDimension' - /// as 'rowHeight'. - /// - parameter estimatedHeight: The estimated average height for the cells. - /// - parameter cellForRowAtIndexPath: The closure that returns a cell for the - /// given index path. - open func useAsDataSource(_ cellForRowAtIndexPath: - @escaping (UITableView, E, IndexPath) -> UITableViewCell) { - self.view?.dataSource = self - self.cellForRowAtIndexPath = cellForRowAtIndexPath - } + /// Configure the TableView to use this adapter as its DataSource. + /// - parameter automaticDimension: If you wish to use 'UITableViewAutomaticDimension' + /// as 'rowHeight'. + /// - parameter estimatedHeight: The estimated average height for the cells. + /// - parameter cellForRowAtIndexPath: The closure that returns a cell for the + /// given index path. + open func useAsDataSource( + _ cellForRowAtIndexPath: + @escaping (UITableView, E, IndexPath) -> UITableViewCell + ) { + self.view?.dataSource = self + self.cellForRowAtIndexPath = cellForRowAtIndexPath + } - /// Tells the data source to return the number of rows in a given section of a table view. - dynamic open func tableView( - _ tableView: UITableView, - numberOfRowsInSection section: Int - ) -> Int { - return self.buffer.currentElements.count - } + /// Tells the data source to return the number of rows in a given section of a table view. + dynamic open func tableView( + _ tableView: UITableView, + numberOfRowsInSection section: Int + ) -> Int { + return self.buffer.currentElements.count + } - /// Asks the data source for a cell to insert in a particular location of the table view. - open func tableView( - _ tableView: UITableView, - cellForRowAt indexPath: IndexPath - ) -> UITableViewCell { - return self.cellForRowAtIndexPath!( - tableView, - buffer.currentElements[(indexPath as NSIndexPath).row], - indexPath) + /// Asks the data source for a cell to insert in a particular location of the table view. + open func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + return self.cellForRowAtIndexPath!( + tableView, + buffer.currentElements[(indexPath as NSIndexPath).row], + indexPath) + } } -} -extension TableViewDiffAdapter: BufferDelegate { - /// Notifies the receiver that the content is about to change. - public func buffer(willChangeContent buffer: BufferType) { - self.view?.beginUpdates() - } + extension TableViewDiffAdapter: BufferDelegate { + /// Notifies the receiver that the content is about to change. + public func buffer(willChangeContent buffer: BufferType) { + self.view?.beginUpdates() + } - /// Notifies the receiver that rows were deleted. - public func buffer(didDeleteElementAtIndices buffer: BufferType, indices: [UInt]) { - let deletionIndexPaths = indices.map({ - IndexPath(row: Int($0), section: self.sectionIndex) - }) - self.view?.deleteRows(at: deletionIndexPaths, with: .fade) - } + /// Notifies the receiver that rows were deleted. + public func buffer(didDeleteElementAtIndices buffer: BufferType, indices: [UInt]) { + let deletionIndexPaths = indices.map({ + IndexPath(row: Int($0), section: self.sectionIndex) + }) + self.view?.deleteRows(at: deletionIndexPaths, with: .fade) + } - /// Notifies the receiver that rows were inserted. - public func buffer(didInsertElementsAtIndices buffer: BufferType, indices: [UInt]) { - let insertionIndexPaths = indices.map({ - IndexPath(row: Int($0), section: self.sectionIndex) - }) - self.view?.insertRows(at: insertionIndexPaths, with: .fade) - } + /// Notifies the receiver that rows were inserted. + public func buffer(didInsertElementsAtIndices buffer: BufferType, indices: [UInt]) { + let insertionIndexPaths = indices.map({ + IndexPath(row: Int($0), section: self.sectionIndex) + }) + self.view?.insertRows(at: insertionIndexPaths, with: .fade) + } - /// Notifies the receiver that a row got moved. - public func buffer(didMoveElement buffer: BufferType, from: UInt, to: UInt) { - self.view?.moveRow( - at: IndexPath(row: Int(from), section: self.sectionIndex), - to: IndexPath(row: Int(to), section: self.sectionIndex)) - } + /// Notifies the receiver that a row got moved. + public func buffer(didMoveElement buffer: BufferType, from: UInt, to: UInt) { + self.view?.moveRow( + at: IndexPath(row: Int(from), section: self.sectionIndex), + to: IndexPath(row: Int(to), section: self.sectionIndex)) + } - /// Notifies the receiver that the content updates has ended. - public func buffer(didChangeContent buffer: BufferType) { - self.view?.endUpdates() - } + /// Notifies the receiver that the content updates has ended. + public func buffer(didChangeContent buffer: BufferType) { + self.view?.endUpdates() + } - /// Called when one of the observed properties for this object changed. - public func buffer(didChangeElementAtIndex buffer: BufferType, index: UInt) { - self.view?.reloadRows( - at: [IndexPath(row: Int(index), section: self.sectionIndex)], - with: .automatic) - } + /// Called when one of the observed properties for this object changed. + public func buffer(didChangeElementAtIndex buffer: BufferType, index: UInt) { + self.view?.reloadRows( + at: [IndexPath(row: Int(index), section: self.sectionIndex)], + with: .automatic) + } - /// Notifies the receiver that the content updates has ended and the whole array changed. - public func buffer(didChangeAllContent buffer: BufferType) { - self.view?.reloadData() + /// Notifies the receiver that the content updates has ended and the whole array changed. + public func buffer(didChangeAllContent buffer: BufferType) { + self.view?.reloadData() + } } -} #endif