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)];