Skip to content

Commit

Permalink
Merge pull request #60 from adya/master
Browse files Browse the repository at this point in the history
Refactored implementation of the fix from merge #56
  • Loading branch information
tonyarnold committed Sep 11, 2019
2 parents 109d103 + 486d2d1 commit 6466b25
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 26 deletions.
36 changes: 30 additions & 6 deletions README.md
Expand Up @@ -83,23 +83,47 @@ However, if we decided to sort it so that deletions and higher indices are proce

### Table and Collection Views

```swift
// The following will automatically animate deletions, insertions, and moves:

tableView.animateRowChanges(oldData: old, newData: new)
The following will automatically animate deletions, insertions, and moves:

collectionView.animateItemChanges(oldData: old, newData: new)
```swift
tableView.animateRowChanges(oldData: old, newData: new)

// It can work with sections, too!
collectionView.animateItemChanges(oldData: old, newData: new, updateData: { self.dataSource = new })
```

It can work with sections, too!
```swift
tableView.animateRowAndSectionChanges(oldData: old, newData: new)

collectionView.animateItemAndSectionChanges(oldData: old, newData: new)
collectionView.animateItemAndSectionChanges(oldData: old, newData: new, updateData: { self.dataSource = new })
```

You can also calculate `diff` separately and use it later:
```swift
// Generate the difference first
let diff = dataSource.diff(newDataSource)

// This will apply changes to dataSource.
let dataSourceUpdate = { self.dataSource = newDataSource }

// ...

tableView.apply(diff)

collectionView.apply(diff, updateData: dataSourceUpdate)
```

Please see the [included examples](/Examples/) for a working sample.

#### Note about `updateData`

Since version `2.0.0` there is now an `updateData` closure which notifies you when it's an appropriate time to update `dataSource` of your `UICollectionView`. This addition refers to UICollectionView's [performbatchUpdates](https://developer.apple.com/documentation/uikit/uicollectionview/1618045-performbatchupdates):

> If the collection view's layout is not up to date before you call this method, a reload may occur. To avoid problems, you should update your data model inside the updates block or ensure the layout is updated before you call `performBatchUpdates(_:completion:)`.
Thus, it is **recommended** to update your `dataSource` inside `updateData` closure to avoid potential crashes during animations.

### Using Patch and Diff

When you want to determine the steps to transform one collection into another (e.g. you want to animate your user interface according to changes in your model), you could do the following:
Expand Down
40 changes: 20 additions & 20 deletions Sources/Differ/Diff+UIKit.swift
Expand Up @@ -253,16 +253,17 @@ public extension UICollectionView {
/// - oldData: Data which reflects the previous state of `UICollectionView`
/// - newData: Data which reflects the current state of `UICollectionView`
/// - indexPathTransform: Closure which transforms zero-based `IndexPath` to desired `IndexPath`
/// - updateData: Closure to be called immediately before performing updates, giving you a chance to correctly update data source
/// - completion: Closure to be executed when the animation completes
func animateItemChanges<T: Collection>(
oldData: T,
newData: T,
indexPathTransform: @escaping (IndexPath) -> IndexPath = { $0 },
updateData: ((T) -> Void)? = nil,
updateData: () -> Void,
completion: ((Bool) -> Void)? = nil
) where T.Element: Equatable {
let diff = oldData.extendedDiff(newData)
apply(diff, data: newData, updateData: updateData, completion: completion, indexPathTransform: indexPathTransform)
apply(diff, updateData: updateData, completion: completion, indexPathTransform: indexPathTransform)
}

/// Animates items which changed between oldData and newData.
Expand All @@ -272,28 +273,28 @@ public extension UICollectionView {
/// - newData: Data which reflects the current state of `UICollectionView`
/// - isEqual: A function comparing two elements of `T`
/// - indexPathTransform: Closure which transforms zero-based `IndexPath` to desired `IndexPath`
/// - updateData: Closure to be called immediately before performing updates, giving you a chance to correctly update data source
/// - completion: Closure to be executed when the animation completes
func animateItemChanges<T: Collection>(
oldData: T,
newData: T,
isEqual: EqualityChecker<T>,
indexPathTransform: @escaping (IndexPath) -> IndexPath = { $0 },
updateData: ((T) -> Void)? = nil,
updateData: () -> Void,
completion: ((Bool) -> Swift.Void)? = nil
) {
let diff = oldData.extendedDiff(newData, isEqual: isEqual)
apply(diff, data: newData, updateData: updateData, completion: completion, indexPathTransform: indexPathTransform)
apply(diff, updateData: updateData, completion: completion, indexPathTransform: indexPathTransform)
}

func apply<T>(
func apply(
_ diff: ExtendedDiff,
data: T,
updateData: ((T) -> Void)? = nil,
updateData: () -> Void,
completion: ((Bool) -> Swift.Void)? = nil,
indexPathTransform: @escaping (IndexPath) -> IndexPath = { $0 }
) {
performBatchUpdates({
updateData?(data)
updateData()
let update = BatchUpdate(diff: diff, indexPathTransform: indexPathTransform)
self.deleteItems(at: update.deletions)
self.insertItems(at: update.insertions)
Expand All @@ -308,21 +309,21 @@ public extension UICollectionView {
/// - newData: Data which reflects the current state of `UICollectionView`
/// - indexPathTransform: Closure which transforms zero-based `IndexPath` to desired `IndexPath`
/// - sectionTransform: Closure which transforms zero-based section(`Int`) into desired section(`Int`)
/// - updateData: Closure to be called immediately before performing updates, giving you a chance to correctly update data source
/// - completion: Closure to be executed when the animation completes
func animateItemAndSectionChanges<T: Collection>(
oldData: T,
newData: T,
indexPathTransform: @escaping (IndexPath) -> IndexPath = { $0 },
sectionTransform: @escaping (Int) -> Int = { $0 },
updateData: ((T) -> Void)? = nil,
updateData: () -> Void,
completion: ((Bool) -> Swift.Void)? = nil
)
where T.Element: Collection,
T.Element: Equatable,
T.Element.Element: Equatable {
self.apply(
oldData.nestedExtendedDiff(to: newData),
data: newData,
indexPathTransform: indexPathTransform,
sectionTransform: sectionTransform,
updateData: updateData,
Expand All @@ -338,14 +339,15 @@ public extension UICollectionView {
/// - isEqualElement: A function comparing two items (elements of `T.Element`)
/// - indexPathTransform: Closure which transforms zero-based `IndexPath` to desired `IndexPath`
/// - sectionTransform: Closure which transforms zero-based section(`Int`) into desired section(`Int`)
/// - updateData: Closure to be called immediately before performing updates, giving you a chance to correctly update data source
/// - completion: Closure to be executed when the animation completes
func animateItemAndSectionChanges<T: Collection>(
oldData: T,
newData: T,
isEqualElement: NestedElementEqualityChecker<T>,
indexPathTransform: @escaping (IndexPath) -> IndexPath = { $0 },
sectionTransform: @escaping (Int) -> Int = { $0 },
updateData: ((T) -> Void)? = nil,
updateData: () -> Void,
completion: ((Bool) -> Swift.Void)? = nil
)
where T.Element: Collection,
Expand All @@ -355,7 +357,6 @@ public extension UICollectionView {
to: newData,
isEqualElement: isEqualElement
),
data: newData,
indexPathTransform: indexPathTransform,
sectionTransform: sectionTransform,
updateData: updateData,
Expand All @@ -371,14 +372,15 @@ public extension UICollectionView {
/// - isEqualSection: A function comparing two sections (elements of `T`)
/// - indexPathTransform: Closure which transforms zero-based `IndexPath` to desired `IndexPath`
/// - sectionTransform: Closure which transforms zero-based section(`Int`) into desired section(`Int`)
/// - updateData: Closure to be called immediately before performing updates, giving you a chance to correctly update data source.
/// - completion: Closure to be executed when the animation completes
func animateItemAndSectionChanges<T: Collection>(
oldData: T,
newData: T,
isEqualSection: EqualityChecker<T>,
indexPathTransform: @escaping (IndexPath) -> IndexPath = { $0 },
sectionTransform: @escaping (Int) -> Int = { $0 },
updateData: ((T) -> Void)? = nil,
updateData: () -> Void,
completion: ((Bool) -> Swift.Void)? = nil
)
where T.Element: Collection,
Expand All @@ -388,7 +390,6 @@ public extension UICollectionView {
to: newData,
isEqualSection: isEqualSection
),
data: newData,
indexPathTransform: indexPathTransform,
sectionTransform: sectionTransform,
updateData: updateData,
Expand All @@ -405,6 +406,7 @@ public extension UICollectionView {
/// - isEqualElement: A function comparing two items (elements of `T.Element`)
/// - indexPathTransform: Closure which transforms zero-based `IndexPath` to desired `IndexPath`
/// - sectionTransform: Closure which transforms zero-based section(`Int`) into desired section(`Int`)
/// - updateData: Closure to be called immediately before performing updates, giving you a chance to correctly update data source
/// - completion: Closure to be executed when the animation completes
func animateItemAndSectionChanges<T: Collection>(
oldData: T,
Expand All @@ -413,7 +415,7 @@ public extension UICollectionView {
isEqualElement: NestedElementEqualityChecker<T>,
indexPathTransform: @escaping (IndexPath) -> IndexPath = { $0 },
sectionTransform: @escaping (Int) -> Int = { $0 },
updateData: ((T) -> Void)? = nil,
updateData: () -> Void,
completion: ((Bool) -> Swift.Void)? = nil
)
where T.Element: Collection {
Expand All @@ -423,24 +425,22 @@ public extension UICollectionView {
isEqualSection: isEqualSection,
isEqualElement: isEqualElement
),
data: newData,
indexPathTransform: indexPathTransform,
sectionTransform: sectionTransform,
updateData: updateData,
completion: completion
)
}

func apply<T: Collection>(
func apply(
_ diff: NestedExtendedDiff,
data: T,
indexPathTransform: @escaping (IndexPath) -> IndexPath = { $0 },
sectionTransform: @escaping (Int) -> Int = { $0 },
updateData: ((T) -> Void)? = nil,
updateData: () -> Void,
completion: ((Bool) -> Void)? = nil
) {
performBatchUpdates({
updateData?(data)
updateData()
let update = NestedBatchUpdate(diff: diff, indexPathTransform: indexPathTransform, sectionTransform: sectionTransform)
self.insertSections(update.sectionInsertions)
self.deleteSections(update.sectionDeletions)
Expand Down

0 comments on commit 6466b25

Please sign in to comment.