Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

* refactor selectors #87

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions Dwifft/AbstractDiffCalculator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ import Foundation

/// A parent class for all diff calculators. Don't use it directly.
public class AbstractDiffCalculator<Section: Equatable, Value: Equatable> {

internal init(initialSectionedValues: SectionedValues<Section, Value>) {
self._sectionedValues = initialSectionedValues
}

/// The number of sections in the diff calculator. Return this inside
/// `numberOfSections(in: tableView)` or `numberOfSections(in: collectionView)`.
/// Don't implement that method any other way (see the docs for `numberOfObjects(inSection:)`
/// for more context).
public final func numberOfSections() -> Int {
return self.sectionedValues.sections.count
}

/// The section at a given index. If you implement `tableView:titleForHeaderInSection` or
/// `collectionView:viewForSupplementaryElementOfKind:atIndexPath`, you can use this
/// method to get information about that section out of Dwifft.
Expand All @@ -32,8 +32,7 @@ public class AbstractDiffCalculator<Section: Equatable, Value: Equatable> {
public final func value(forSection: Int) -> Section {
return self.sectionedValues[forSection].0
}



/// The, uh, number of objects in a given section. Use this to implement
/// `UITableViewDataSource.numberOfRowsInSection:` or `UICollectionViewDataSource.numberOfItemsInSection:`.
/// Seriously, don't implement that method any other way - there is some subtle timing stuff
Expand All @@ -46,8 +45,7 @@ public class AbstractDiffCalculator<Section: Equatable, Value: Equatable> {
public final func numberOfObjects(inSection section: Int) -> Int {
return self.sectionedValues[section].1.count
}



/// The value at a given index path. Use this to implement
/// `UITableViewDataSource.cellForRowAtIndexPath` or `UICollectionViewDataSource.cellForItemAtIndexPath`.
///
Expand All @@ -62,8 +60,7 @@ public class AbstractDiffCalculator<Section: Equatable, Value: Equatable> {
#endif
return self.sectionedValues[indexPath.section].1[row]
}



/// Set this variable to automatically trigger the correct section/row/item insertion/deletions
/// on your table/collection view.
public final var sectionedValues: SectionedValues<Section, Value> {
Expand All @@ -79,12 +76,12 @@ public class AbstractDiffCalculator<Section: Equatable, Value: Equatable> {
}
}
}

internal static func buildSectionedValues(values: [Value], sectionIndex: Int) -> SectionedValues<Int, Value> {
let firstRows = (0..<sectionIndex).map { ($0, [Value]()) }
return SectionedValues(firstRows + [(sectionIndex, values)])
}

// UITableView and UICollectionView both perform assertions on the *current* number of rows/items before performing any updates. As such, the `sectionedValues` property must be backed by an internal value that does not change until *after* `beginUpdates`/`performBatchUpdates` has been called.
internal final var _sectionedValues: SectionedValues<Section, Value>
internal func processChanges(newState: SectionedValues<Section, Value>, diff: [SectionedDiffStep<Section, Value>]){
Expand Down
15 changes: 7 additions & 8 deletions Dwifft/Dwifft+AppKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#if os(OSX)

import Cocoa

/// This class manages a `NSTableView`'s rows. It will make the necessary
/// calls to the table view to ensure that its UI is kept in sync with the contents of the `rows` property.
public final class TableViewDiffCalculator<Value: Equatable>: AbstractDiffCalculator<Int, Value> {
Expand All @@ -23,7 +23,7 @@ public final class TableViewDiffCalculator<Value: Equatable>: AbstractDiffCalcul
/// You can change insertion/deletion animations like this! Fade works well.
/// So does Top/Bottom. Left/Right/Middle are a little weird, but hey, do your thing.
public var insertionAnimation = NSTableView.AnimationOptions.slideUp

public var deletionAnimation = NSTableView.AnimationOptions.slideUp

/// Set this variable to automatically trigger the correct row insertion/deletions
Expand All @@ -48,7 +48,7 @@ public final class TableViewDiffCalculator<Value: Equatable>: AbstractDiffCalcul
self.sectionIndex = sectionIndex
super.init(initialSectionedValues: AbstractDiffCalculator<Int, Value>.buildSectionedValues(values: initialRows, sectionIndex: sectionIndex))
}

override internal func processChanges(newState: SectionedValues<Int, Value>, diff: [SectionedDiffStep<Int, Value>]) {
guard let tableView = self.tableView else { return }
tableView.beginUpdates()
Expand Down Expand Up @@ -102,18 +102,17 @@ public final class SingleSectionCollectionViewDiffCalculator<Value: Equatable> {
}

private let internalDiffCalculator: CollectionViewDiffCalculator<Int, Value>

}


/// This class manages a `NSCollectionView`'s items and sections. It will make the necessary
/// calls to the collection view to ensure that its UI is kept in sync with the contents
/// of the `sectionedValues` property.
public final class CollectionViewDiffCalculator<Section: Equatable, Value: Equatable> : AbstractDiffCalculator<Section, Value> {

/// The collection view to be managed.
public weak var collectionView: NSCollectionView?

/// Initializes a new diff calculator.
///
/// - Parameters:
Expand All @@ -123,7 +122,7 @@ public final class CollectionViewDiffCalculator<Section: Equatable, Value: Equat
self.collectionView = collectionView
super.init(initialSectionedValues: initialSectionedValues)
}

override internal func processChanges(newState: SectionedValues<Section, Value>, diff: [SectionedDiffStep<Section, Value>]) {
guard let collectionView = self.collectionView else { return }
collectionView.animator().performBatchUpdates({
Expand Down
4 changes: 2 additions & 2 deletions Dwifft/Dwifft+UIKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public final class SingleSectionTableViewDiffCalculator<Value: Equatable> {
self.internalDiffCalculator.insertionAnimation = self.insertionAnimation
}
}

public var deletionAnimation = UITableViewRowAnimation.automatic {
didSet {
self.internalDiffCalculator.deletionAnimation = self.deletionAnimation
Expand Down Expand Up @@ -182,7 +182,7 @@ public final class SingleSectionCollectionViewDiffCalculator<Value: Equatable> {
}

private let internalDiffCalculator: CollectionViewDiffCalculator<Int, Value>

}

#endif
6 changes: 3 additions & 3 deletions Dwifft/Dwifft.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public enum DiffStep<Value> : CustomDebugStringConvertible {
case delete(Int, Value)

public var debugDescription: String {
switch(self) {
switch self {
case let .insert(i, j):
return "+\(j)@\(i)"
case let .delete(i, j):
Expand All @@ -25,7 +25,7 @@ public enum DiffStep<Value> : CustomDebugStringConvertible {

/// The index to be inserted or deleted.
public var idx: Int {
switch(self) {
switch self {
case let .insert(i, _):
return i
case let .delete(i, _):
Expand All @@ -35,7 +35,7 @@ public enum DiffStep<Value> : CustomDebugStringConvertible {

/// The value to be inserted or deleted.
public var value: Value {
switch(self) {
switch self {
case let .insert(j):
return j.1
case let .delete(j):
Expand Down
1 change: 0 additions & 1 deletion Dwifft/SectionedValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,4 @@ public extension SectionedValues where Section: Hashable {
return (section, sortedValues)
})
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class StuffCollectionViewCell: UICollectionViewCell {
label.textAlignment = .center
self.addSubview(label)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Expand All @@ -40,7 +40,7 @@ final class StuffSectionHeaderView: UICollectionReusableView {
label.font = UIFont.italicSystemFont(ofSize: 14)
self.addSubview(label)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Expand All @@ -55,7 +55,7 @@ final class StuffCollectionViewController: UICollectionViewController {

required init!(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Shuffle", style: .plain, target: self, action: #selector(StuffCollectionViewController.shuffle))
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Shuffle", style: .plain, target: self, action: #selector(shuffle))
}

@objc func shuffle() {
Expand Down
14 changes: 7 additions & 7 deletions DwifftExample/DwifftExample-iOS/StuffTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,27 @@ final class StuffTableViewController: UITableViewController {

required init!(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Shuffle", style: .plain, target: self, action: #selector(StuffTableViewController.shuffle))
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Shuffle", style: .plain, target: self, action: #selector(shuffle))
}

@objc func shuffle() {
self.stuff = Stuff.wordStuff()
}

var diffCalculator: TableViewDiffCalculator<String, String>?

var stuff: SectionedValues<String, String> = Stuff.wordStuff() {
// So, whenever your datasource's array of things changes, just let the diffCalculator know and it'll do the rest.
didSet {
self.diffCalculator?.sectionedValues = stuff
}
}

override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "reuseIdentifier")
self.diffCalculator = TableViewDiffCalculator(tableView: self.tableView, initialSectionedValues: self.stuff)

// You can change insertion/deletion animations like this! Automatic works for most situations. Fade works well too. So does Top/Bottom. Left/Right/Middle are a little weird, but hey, do your thing.
self.diffCalculator?.insertionAnimation = .fade
self.diffCalculator?.deletionAnimation = .fade
Expand All @@ -46,7 +46,7 @@ final class StuffTableViewController: UITableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return self.diffCalculator?.numberOfSections() ?? 0
}

/// IMPORTANT: you *must* implement `numberOfSections` this way (meaning, using this function on your diff calculator) in your app, to avoid a lot of gotchas around UITableView's internal assertions.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.diffCalculator?.numberOfObjects(inSection: section) ?? 0
Expand Down
2 changes: 1 addition & 1 deletion DwifftTests/DwifftTests-macOS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ class DwifftTests: XCTestCase {
self.view = NSView()
}
}

class TestCollectionView: NSCollectionView {

let insertionExpectations: [Int: XCTestExpectation]
Expand Down
4 changes: 2 additions & 2 deletions DwifftTests/DwifftTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -432,11 +432,11 @@ class DwifftTests: XCTestCase {
let x: XCTestExpectation = expectation(description: "+\(i)")
deletionExpectations[i] = x
}

let collectionView = TestCollectionView(insertionExpectations: insertionExpectations, deletionExpectations: deletionExpectations)
let viewController = TestViewController(collectionView: collectionView, rows: [0, 1, 2, 5, 8, 9, 0])
viewController.rows = [4, 5, 9, 8, 3, 1, 0]
waitForExpectations(timeout: 1.0, handler: nil)
}

}