Skip to content

Commit e0af2d1

Browse files
Mikhail Markinkzaher
authored andcommitted
Crash resolved by setting sections within updateBlock.
1 parent 153d788 commit e0af2d1

File tree

4 files changed

+84
-112
lines changed

4 files changed

+84
-112
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
77
* Reduce computational complexity. #242
88
* Adapted for RxSwift 4.2
99
* Makes SectionModel and AnimatableSectionModel equatable when their model and items conforms to equatable.
10+
* Resolved issues with Animated Collection View #60
1011

1112
## [3.1.0](https://github.com/RxSwiftCommunity/RxDataSources/releases/tag/3.1.0)
1213

Sources/RxDataSources/RxCollectionViewSectionedAnimatedDataSource.swift

Lines changed: 38 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ import RxCocoa
1515
#endif
1616
import Differentiator
1717

18-
/*
19-
This is commented becuse collection view has bugs when doing animated updates.
20-
Take a look at randomized sections.
21-
*/
2218
open class RxCollectionViewSectionedAnimatedDataSource<S: AnimatableSectionModelType>
2319
: CollectionViewSectionedDataSource<S>
2420
, RxCollectionViewDataSourceType {
@@ -47,69 +43,12 @@ open class RxCollectionViewSectionedAnimatedDataSource<S: AnimatableSectionModel
4743
moveItem: moveItem,
4844
canMoveItemAtIndexPath: canMoveItemAtIndexPath
4945
)
50-
51-
self.partialUpdateEvent
52-
// so in case it does produce a crash, it will be after the data has changed
53-
.observeOn(MainScheduler.asyncInstance)
54-
// Collection view has issues digesting fast updates, this should
55-
// help to alleviate the issues with them.
56-
.throttle(0.5, scheduler: MainScheduler.instance)
57-
.subscribe(onNext: { [weak self] event in
58-
self?.collectionView(event.0, throttledObservedEvent: event.1)
59-
})
60-
.disposed(by: disposeBag)
6146
}
62-
63-
// For some inexplicable reason, when doing animated updates first time
64-
// it crashes. Still need to figure out that one.
47+
48+
// there is no longer limitation to load initial sections with reloadData
49+
// but it is kept as a feature everyone got used to
6550
var dataSet = false
6651

67-
private let disposeBag = DisposeBag()
68-
69-
// This subject and throttle are here
70-
// because collection view has problems processing animated updates fast.
71-
// This should somewhat help to alleviate the problem.
72-
private let partialUpdateEvent = PublishSubject<(UICollectionView, Event<Element>)>()
73-
74-
/**
75-
This method exists because collection view updates are throttled because of internal collection view bugs.
76-
Collection view behaves poorly during fast updates, so this should remedy those issues.
77-
*/
78-
open func collectionView(_ collectionView: UICollectionView, throttledObservedEvent event: Event<Element>) {
79-
Binder(self) { dataSource, newSections in
80-
let oldSections = dataSource.sectionModels
81-
do {
82-
// if view is not in view hierarchy, performing batch updates will crash the app
83-
if collectionView.window == nil {
84-
dataSource.setSections(newSections)
85-
collectionView.reloadData()
86-
return
87-
}
88-
let differences = try Diff.differencesForSectionedView(initialSections: oldSections, finalSections: newSections)
89-
90-
switch self.decideViewTransition(self, collectionView, differences) {
91-
case .animated:
92-
for difference in differences {
93-
dataSource.setSections(difference.finalSections)
94-
95-
collectionView.performBatchUpdates(difference, animationConfiguration: self.animationConfiguration)
96-
}
97-
case .reload:
98-
self.setSections(newSections)
99-
collectionView.reloadData()
100-
}
101-
}
102-
catch let e {
103-
#if DEBUG
104-
print("Error while binding data animated: \(e)\nFallback to normal `reloadData` behavior.")
105-
rxDebugFatalError(e)
106-
#endif
107-
self.setSections(newSections)
108-
collectionView.reloadData()
109-
}
110-
}.on(event)
111-
}
112-
11352
open func collectionView(_ collectionView: UICollectionView, observedEvent: Event<Element>) {
11453
Binder(self) { dataSource, newSections in
11554
#if DEBUG
@@ -121,8 +60,41 @@ open class RxCollectionViewSectionedAnimatedDataSource<S: AnimatableSectionModel
12160
collectionView.reloadData()
12261
}
12362
else {
124-
let element = (collectionView, observedEvent)
125-
dataSource.partialUpdateEvent.on(.next(element))
63+
DispatchQueue.main.async {
64+
// if view is not in view hierarchy, performing batch updates will crash the app
65+
if collectionView.window == nil {
66+
dataSource.setSections(newSections)
67+
collectionView.reloadData()
68+
return
69+
}
70+
let oldSections = dataSource.sectionModels
71+
do {
72+
let differences = try Diff.differencesForSectionedView(initialSections: oldSections, finalSections: newSections)
73+
74+
switch self.decideViewTransition(self, collectionView, differences) {
75+
case .animated:
76+
// each difference must be run in a separate performBatchUpdate, otherwise it crashes.
77+
// this is a limitation of Diff tool
78+
for difference in differences {
79+
collectionView.performBatchUpdates({ [unowned self] in
80+
// sections must be set within updateBlock in performBatchUpdates
81+
dataSource.setSections(difference.finalSections)
82+
collectionView.batchUpdates(difference, animationConfiguration: self.animationConfiguration)
83+
}, completion: nil)
84+
}
85+
86+
case .reload:
87+
self.setSections(newSections)
88+
collectionView.reloadData()
89+
return
90+
}
91+
}
92+
catch let e {
93+
rxDebugFatalError(e)
94+
self.setSections(newSections)
95+
collectionView.reloadData()
96+
}
97+
}
12698
}
12799
}.on(observedEvent)
128100
}

Sources/RxDataSources/RxTableViewSectionedAnimatedDataSource.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,23 @@ open class RxTableViewSectionedAnimatedDataSource<S: AnimatableSectionModelType>
9999

100100
switch self.decideViewTransition(self, tableView, differences) {
101101
case .animated:
102+
// each difference must be run in a separate performBatchUpdate, otherwise it crashes.
103+
// this is a limitation of Diff tool
102104
for difference in differences {
103-
dataSource.setSections(difference.finalSections)
104-
105-
tableView.performBatchUpdates(difference, animationConfiguration: self.animationConfiguration)
105+
let updateBlock = { [unowned self] in
106+
// sections must be set within updateBlock in performBatchUpdates
107+
dataSource.setSections(difference.finalSections)
108+
tableView.batchUpdates(difference, animationConfiguration: self.animationConfiguration)
109+
}
110+
if #available(iOS 11, *) {
111+
tableView.performBatchUpdates(updateBlock, completion: nil)
112+
} else {
113+
tableView.beginUpdates()
114+
updateBlock()
115+
tableView.endUpdates()
116+
}
106117
}
118+
107119
case .reload:
108120
self.setSections(newSections)
109121
tableView.reloadData()

Sources/RxDataSources/UI+SectionedViewType.swift

Lines changed: 30 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,6 @@ extension UITableView : SectionedViewType {
5656
public func reloadSections(_ sections: [Int], animationStyle: UITableViewRowAnimation) {
5757
self.reloadSections(indexSet(sections), with: animationStyle)
5858
}
59-
60-
public func performBatchUpdates<S>(_ changes: Changeset<S>, animationConfiguration: AnimationConfiguration) {
61-
self.beginUpdates()
62-
_performBatchUpdates(self, changes: changes, animationConfiguration: animationConfiguration)
63-
self.endUpdates()
64-
}
6559
}
6660

6761
extension UICollectionView : SectionedViewType {
@@ -96,13 +90,6 @@ extension UICollectionView : SectionedViewType {
9690
public func reloadSections(_ sections: [Int], animationStyle: UITableViewRowAnimation) {
9791
self.reloadSections(indexSet(sections))
9892
}
99-
100-
public func performBatchUpdates<S>(_ changes: Changeset<S>, animationConfiguration: AnimationConfiguration) {
101-
self.performBatchUpdates({ () -> Void in
102-
_performBatchUpdates(self, changes: changes, animationConfiguration: animationConfiguration)
103-
}, completion: { (completed: Bool) -> Void in
104-
})
105-
}
10693
}
10794

10895
public protocol SectionedViewType {
@@ -116,39 +103,39 @@ public protocol SectionedViewType {
116103
func moveSection(_ from: Int, to: Int)
117104
func reloadSections(_ sections: [Int], animationStyle: UITableViewRowAnimation)
118105

119-
func performBatchUpdates<S>(_ changes: Changeset<S>, animationConfiguration: AnimationConfiguration)
106+
func batchUpdates<S>(_ changes: Changeset<S>, animationConfiguration: AnimationConfiguration)
120107
}
121108

122-
func _performBatchUpdates<V: SectionedViewType, S>(_ view: V, changes: Changeset<S>, animationConfiguration:AnimationConfiguration) {
123-
typealias I = S.Item
124-
125-
view.deleteSections(changes.deletedSections, animationStyle: animationConfiguration.deleteAnimation)
126-
// Updated sections doesn't mean reload entire section, somebody needs to update the section view manually
127-
// otherwise all cells will be reloaded for nothing.
128-
//view.reloadSections(changes.updatedSections, animationStyle: rowAnimation)
129-
view.insertSections(changes.insertedSections, animationStyle: animationConfiguration.insertAnimation)
130-
for (from, to) in changes.movedSections {
131-
view.moveSection(from, to: to)
132-
}
133-
134-
view.deleteItemsAtIndexPaths(
135-
changes.deletedItems.map { IndexPath(item: $0.itemIndex, section: $0.sectionIndex) },
136-
animationStyle: animationConfiguration.deleteAnimation
137-
)
138-
view.insertItemsAtIndexPaths(
139-
changes.insertedItems.map { IndexPath(item: $0.itemIndex, section: $0.sectionIndex) },
140-
animationStyle: animationConfiguration.insertAnimation
141-
)
142-
view.reloadItemsAtIndexPaths(
143-
changes.updatedItems.map { IndexPath(item: $0.itemIndex, section: $0.sectionIndex) },
144-
animationStyle: animationConfiguration.reloadAnimation
145-
)
146-
147-
for (from, to) in changes.movedItems {
148-
view.moveItemAtIndexPath(
149-
IndexPath(item: from.itemIndex, section: from.sectionIndex),
150-
to: IndexPath(item: to.itemIndex, section: to.sectionIndex)
109+
extension SectionedViewType {
110+
public func batchUpdates<S>(_ changes: Changeset<S>, animationConfiguration: AnimationConfiguration) {
111+
typealias I = S.Item
112+
113+
deleteSections(changes.deletedSections, animationStyle: animationConfiguration.deleteAnimation)
114+
115+
insertSections(changes.insertedSections, animationStyle: animationConfiguration.insertAnimation)
116+
for (from, to) in changes.movedSections {
117+
moveSection(from, to: to)
118+
}
119+
120+
deleteItemsAtIndexPaths(
121+
changes.deletedItems.map { IndexPath(item: $0.itemIndex, section: $0.sectionIndex) },
122+
animationStyle: animationConfiguration.deleteAnimation
123+
)
124+
insertItemsAtIndexPaths(
125+
changes.insertedItems.map { IndexPath(item: $0.itemIndex, section: $0.sectionIndex) },
126+
animationStyle: animationConfiguration.insertAnimation
127+
)
128+
reloadItemsAtIndexPaths(
129+
changes.updatedItems.map { IndexPath(item: $0.itemIndex, section: $0.sectionIndex) },
130+
animationStyle: animationConfiguration.reloadAnimation
151131
)
132+
133+
for (from, to) in changes.movedItems {
134+
moveItemAtIndexPath(
135+
IndexPath(item: from.itemIndex, section: from.sectionIndex),
136+
to: IndexPath(item: to.itemIndex, section: to.sectionIndex)
137+
)
138+
}
152139
}
153140
}
154141
#endif

0 commit comments

Comments
 (0)