From dd3b9eddccd637ff13f5a55ce761c367b441ba44 Mon Sep 17 00:00:00 2001 From: Peter Edmonston Date: Tue, 10 Jan 2017 14:40:53 -0800 Subject: [PATCH] Fix for #378 - emptyView not updated when items added Summary: Modified `IGListAdapter` to update the `hidden` status of the `emptyView` after any item-level changes (`insertInSectionController:atIndexes:` and related methods) based on whether the item count is zero. Closes #378 - [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 added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes. - [x] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md) Closes https://github.com/Instagram/IGListKit/pull/395 Differential Revision: D4400155 Pulled By: jessesquires fbshipit-source-id: 6c3422297d86947e7e2878ba1b9227ff2a2a18b4 --- CHANGELOG.md | 11 ++++++++ Source/IGListAdapter.m | 31 +++++++++++++++++++--- Tests/IGListAdapterTests.m | 54 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afc58d8a..d72bb8e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instagram/IGListKit/releases) on GitHub. +2.2.0 +----- + +This release closes the [2.2.0 milestone](https://github.com/Instagram/IGListKit/milestone/4). + +### Enhancements + +### Fixes + +- Fix bug where emptyView's hidden status is not updated after the number of items is changed with `insertInSectionController:atIndexes:` or related methods. [Peter Edmonston](https://github.com/edmonston) [(#395)](https://github.com/Instagram/IGListKit/pull/395) + 2.1.0 ----- diff --git a/Source/IGListAdapter.m b/Source/IGListAdapter.m index ea55cd16..ad574a8f 100644 --- a/Source/IGListAdapter.m +++ b/Source/IGListAdapter.m @@ -504,10 +504,13 @@ itemCount += [sectionController numberOfItems]; } - [self updateBackgroundViewWithItemCount:itemCount]; + [self updateBackgroundViewShouldHide:itemCount > 0]; } -- (void)updateBackgroundViewWithItemCount:(NSUInteger)itemCount { +- (void)updateBackgroundViewShouldHide:(BOOL)shouldHide { + if (self.isInUpdateBlock) { + return; // will be called again when update block completes + } UIView *backgroundView = [self.dataSource emptyViewForListAdapter:self]; // don't do anything if the client is using the same view if (backgroundView != _collectionView.backgroundView) { @@ -516,7 +519,18 @@ [_collectionView.backgroundView removeFromSuperview]; _collectionView.backgroundView = backgroundView; } - _collectionView.backgroundView.hidden = itemCount > 0; + _collectionView.backgroundView.hidden = shouldHide; +} + +- (BOOL)itemCountIsZero { + __block BOOL isZero = YES; + [self.sectionMap enumerateUsingBlock:^(id _Nonnull object, IGListSectionController * _Nonnull sectionController, NSInteger section, BOOL * _Nonnull stop) { + if (sectionController.numberOfItems > 0) { + isZero = NO; + *stop = YES; + } + }]; + return isZero; } - (IGListSectionMap *)sectionMapAdjustForUpdateBlock:(BOOL)adjustForUpdateBlock { @@ -918,6 +932,7 @@ } else { NSArray *indexPaths = [self indexPathsFromSectionController:sectionController indexes:indexes adjustForUpdateBlock:YES]; [self.updater reloadItemsInCollectionView:collectionView indexPaths:indexPaths]; + [self updateBackgroundViewShouldHide:![self itemCountIsZero]]; } } @@ -934,6 +949,7 @@ NSArray *indexPaths = [self indexPathsFromSectionController:sectionController indexes:indexes adjustForUpdateBlock:NO]; [self.updater insertItemsIntoCollectionView:collectionView indexPaths:indexPaths]; + [self updateBackgroundViewShouldHide:![self itemCountIsZero]]; } - (void)deleteInSectionController:(IGListSectionController *)sectionController atIndexes:(NSIndexSet *)indexes { @@ -949,6 +965,7 @@ NSArray *indexPaths = [self indexPathsFromSectionController:sectionController indexes:indexes adjustForUpdateBlock:YES]; [self.updater deleteItemsFromCollectionView:collectionView indexPaths:indexPaths]; + [self updateBackgroundViewShouldHide:![self itemCountIsZero]]; } - (void)reloadSectionController:(IGListSectionController *)sectionController { @@ -965,6 +982,7 @@ NSIndexSet *sections = [NSIndexSet indexSetWithIndex:section]; [self.updater reloadCollectionView:collectionView sections:sections]; + [self updateBackgroundViewShouldHide:![self itemCountIsZero]]; } - (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion { @@ -978,7 +996,12 @@ weakSelf.isInUpdateBlock = YES; updates(); weakSelf.isInUpdateBlock = NO; - } completion:completion]; + } completion: ^(BOOL finished) { + [weakSelf updateBackgroundViewShouldHide:![weakSelf itemCountIsZero]]; + if (completion) { + completion(finished); + } + }]; } - (void)scrollToSectionController:(IGListSectionController *)sectionController diff --git a/Tests/IGListAdapterTests.m b/Tests/IGListAdapterTests.m index 0dbfe0b1..82c38453 100644 --- a/Tests/IGListAdapterTests.m +++ b/Tests/IGListAdapterTests.m @@ -325,6 +325,60 @@ XCTAssertEqual(CGPointEqualToPoint(point, p), YES); \ XCTAssertTrue(self.collectionView.backgroundView.hidden); } +- (void)test_whenInsertingIntoEmptySection_thatEmptyViewBecomesHidden { + self.dataSource.objects = @[@0]; + self.dataSource.backgroundView = [UIView new]; + [self.adapter reloadDataWithCompletion:nil]; + XCTAssertFalse(self.collectionView.backgroundView.hidden); + IGListTestSection *sectionController = [self.adapter sectionControllerForObject:@(0)]; + sectionController.items = 1; + [self.adapter insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]]; + XCTAssertTrue(self.collectionView.backgroundView.hidden); +} + +- (void)test_whenDeletingAllItemsFromSection_thatEmptyViewBecomesVisible { + self.dataSource.objects = @[@1]; + self.dataSource.backgroundView = [UIView new]; + [self.adapter reloadDataWithCompletion:nil]; + XCTAssertTrue(self.collectionView.backgroundView.hidden); + IGListTestSection *sectionController = [self.adapter sectionControllerForObject:@(1)]; + sectionController.items = 0; + [self.adapter deleteInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]]; + XCTAssertFalse(self.collectionView.backgroundView.hidden); +} + +- (void)test_whenEmptySectionAddsItems_thatEmptyViewBecomesHidden { + self.dataSource.objects = @[@0]; + self.dataSource.backgroundView = [UIView new]; + [self.adapter reloadDataWithCompletion:nil]; + XCTAssertFalse(self.collectionView.backgroundView.hidden); + IGListTestSection *sectionController = [self.adapter sectionControllerForObject:@(0)]; + sectionController.items = 2; + [self.adapter reloadSectionController:sectionController]; + XCTAssertTrue(self.collectionView.backgroundView.hidden); +} + +- (void)test_whenSectionItemsAreDeletedAsBatch_thatEmptyViewBecomesVisible { + self.dataSource.objects = @[@1, @2]; + self.dataSource.backgroundView = [UIView new]; + [self.adapter reloadDataWithCompletion:nil]; + XCTAssertTrue(self.collectionView.backgroundView.hidden); + IGListTestSection *firstSectionController = [self.adapter sectionControllerForObject:@(1)]; + IGListTestSection *secondSectionController = [self.adapter sectionControllerForObject:@(2)]; + XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self.adapter performBatchAnimated:YES updates:^{ + firstSectionController.items = 0; + [self.adapter deleteInSectionController:firstSectionController atIndexes:[NSIndexSet indexSetWithIndex:0]]; + secondSectionController.items = 0; + NSIndexSet *indexesToDelete = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]; + [self.adapter deleteInSectionController:secondSectionController atIndexes:indexesToDelete]; + } completion:^(BOOL finished) { + XCTAssertFalse(self.collectionView.backgroundView.hidden); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + - (void)test_whenScrollViewDelegateSet_thatDelegateReceivesEvents { id mockDelegate = [OCMockObject mockForProtocol:@protocol(UIScrollViewDelegate)];