From 1765d68406300731b9545cfe65a0716907eebb18 Mon Sep 17 00:00:00 2001 From: Ryan Nystrom Date: Thu, 15 Dec 2016 10:50:34 -0800 Subject: [PATCH] Invalidate layout when using reloadData Summary: Always invalidate the `collectionViewLayout` when calling `reloadData`. Turns out there are inconsistencies when layout attributes are queries/stored using `UICollectionViewFlowLayout` and `estimatedItemSize`. Reproduced in unit test, fixed the test. Closes #305 - [x] All tests pass. Demo project builds and runs. - [x] I added tests, an experiment, or detailed why my change isn't tested. - [x] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md) Closes https://github.com/Instagram/IGListKit/pull/319 Differential Revision: D4329318 Pulled By: rnystrom fbshipit-source-id: 91851f6ab170a416810712308727225404ad59ba --- Source/IGListAdapterUpdater.m | 1 + Tests/IGListAdapterUpdaterTests.m | 41 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Source/IGListAdapterUpdater.m b/Source/IGListAdapterUpdater.m index b1b24634..0a9521f7 100644 --- a/Source/IGListAdapterUpdater.m +++ b/Source/IGListAdapterUpdater.m @@ -84,6 +84,7 @@ [delegate listAdapterUpdater:self willReloadDataWithCollectionView:collectionView]; [collectionView reloadData]; + [collectionView.collectionViewLayout invalidateLayout]; [collectionView layoutIfNeeded]; [delegate listAdapterUpdater:self didReloadDataWithCollectionView:collectionView]; diff --git a/Tests/IGListAdapterUpdaterTests.m b/Tests/IGListAdapterUpdaterTests.m index 39095840..f5c85bb0 100644 --- a/Tests/IGListAdapterUpdaterTests.m +++ b/Tests/IGListAdapterUpdaterTests.m @@ -418,4 +418,45 @@ [mockDelegate verify]; } +- (void)test_whenCallingReloadData_withUICollectionViewFlowLayout_withEstimatedSize_thatSectionItemCountsCorrect { + UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; + // setting the estimated size of a layout causes UICollectionView to requery layout attributes during reloadData + // this becomes out of sync with the data source if the section/item count changes + layout.estimatedItemSize = CGSizeMake(100, 10); + + UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) collectionViewLayout:layout]; + IGListTestUICollectionViewDataSource *dataSource = [[IGListTestUICollectionViewDataSource alloc] initWithCollectionView:collectionView]; + + // 2 sections, 1 item in 1st, 4 items in 2nd + dataSource.sections = @[ + [IGSectionObject sectionWithObjects:@[@1]], + [IGSectionObject sectionWithObjects:@[@1, @2, @3, @4]] + ]; + + // assert the initial state of the collection view WITHOUT any layoutSubviews or anything + XCTAssertEqual([collectionView numberOfSections], 2); + XCTAssertEqual([collectionView numberOfItemsInSection:0], 1); + XCTAssertEqual([collectionView numberOfItemsInSection:1], 4); + + dataSource.sections = @[ + [IGSectionObject sectionWithObjects:@[@1]], + ]; + + IGListAdapterUpdater *updater = [IGListAdapterUpdater new]; + [updater performReloadDataWithCollectionView:collectionView]; + + XCTAssertEqual([collectionView numberOfSections], 1); + XCTAssertEqual([collectionView numberOfItemsInSection:0], 1); + + dataSource.sections = @[ + [IGSectionObject sectionWithObjects:@[@1]], + [IGSectionObject sectionWithObjects:@[@1, @2, @3, @4]] + ]; + [updater performReloadDataWithCollectionView:collectionView]; + + XCTAssertEqual([collectionView numberOfSections], 2); + XCTAssertEqual([collectionView numberOfItemsInSection:0], 1); + XCTAssertEqual([collectionView numberOfItemsInSection:1], 4); +} + @end