diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d2e741e..9a54297e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,11 +24,6 @@ This release closes the [3.0.0 milestone](https://github.com/Instagram/IGListKit - Changed `hasChanges` methods in `IGListIndexPathResult` and `IGListIndexSetResult` to read-only properties. [Bofei Zhu](https://github.com/zhubofei) [(#453)](https://github.com/Instagram/IGListKit/pull/453) -### Enhancements - -- Empty Views now move with Refresh Controls, and no longer use the `_collectionView.backgroundView` property. [dshahidehpour](https://github.com/dshahidehpour) [(#462)](https://github.com/Instagram/IGListKit/pull/462)] - - ### Enhancements - You can now manually move items (cells) within a section controller, ex: `[self.collectionContext moveInSectionController:self fromIndex:0 toIndex:1]`. [Ryan Nystrom](https://github.com/rnystrom) [(#418)](https://github.com/Instagram/IGListKit/pull/418) diff --git a/Source/IGListAdapter.m b/Source/IGListAdapter.m index 5848a4b0..bee238a6 100644 --- a/Source/IGListAdapter.m +++ b/Source/IGListAdapter.m @@ -540,22 +540,15 @@ if (self.isInUpdateBlock) { return; // will be called again when update block completes } - - if (shouldHide) { - _emptyListView.hidden = YES; - } else { - UIView *newBackgroundView = [self.dataSource emptyViewForListAdapter:self]; - if (_emptyListView != newBackgroundView) { - [_emptyListView removeFromSuperview]; - _emptyListView = newBackgroundView; - } - - if (_emptyListView != nil) { - _emptyListView.frame = _collectionView.bounds; - _emptyListView.hidden = NO; - [_collectionView addSubview:(id)_emptyListView]; - } + UIView *backgroundView = [self.dataSource emptyViewForListAdapter:self]; + // don't do anything if the client is using the same view + if (backgroundView != _collectionView.backgroundView) { + // collection view will just stack the background views underneath each other if we do not remove the previous + // one first. also fine if it is nil + [_collectionView.backgroundView removeFromSuperview]; + _collectionView.backgroundView = backgroundView; } + _collectionView.backgroundView.hidden = shouldHide; } - (BOOL)itemCountIsZero { diff --git a/Source/IGListAdapterDataSource.h b/Source/IGListAdapterDataSource.h index 41c20cf0..f45ca43e 100644 --- a/Source/IGListAdapterDataSource.h +++ b/Source/IGListAdapterDataSource.h @@ -56,8 +56,9 @@ NS_ASSUME_NONNULL_BEGIN @return A view to use as the collection view background, or `nil` if you don't want a background view. - @note This method is lazily evaluated, meaning, we won't ask for the empty view until it is necessary to display it. - You are free to return new views every time, but for performance reasons you may want to retain the view and return it here. + @note This method is called every time the list adapter is updated. You are free to return new views every time, + but for performance reasons you may want to retain the view and return it here. The infra is only responsible for + adding the background view and maintaining its visibility. */ - (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter; diff --git a/Source/Internal/IGListAdapterInternal.h b/Source/Internal/IGListAdapterInternal.h index 1afa2125..a8dac24b 100644 --- a/Source/Internal/IGListAdapterInternal.h +++ b/Source/Internal/IGListAdapterInternal.h @@ -40,7 +40,7 @@ IGListCollectionContext @property (nonatomic, strong, nullable) IGListAdapterProxy *delegateProxy; -@property (nonatomic, strong, nullable) UIView *emptyListView; +@property (nonatomic, strong, nullable) UIView *emptyBackgroundView; /** When making object updates inside a batch update block, delete operations must use the section /before/ any moves take diff --git a/Tests/IGListAdapterTests.m b/Tests/IGListAdapterTests.m index 8b1ffd68..acc5d28b 100644 --- a/Tests/IGListAdapterTests.m +++ b/Tests/IGListAdapterTests.m @@ -181,40 +181,19 @@ XCTAssertEqual(CGPointEqualToPoint(point, p), YES); \ XCTAssertEqualObjects(identifier, @"IGNibNameUICollectionViewCell"); } -- (void)test_EmptyViewIsCreatedLazily { - UIView *emptyView = [[UIView alloc] init]; - self.dataSource.emptyView = emptyView; +- (void)test_whenDataSourceChanges_thatBackgroundViewVisibilityChanges { self.dataSource.objects = @[@1]; + UIView *background = [[UIView alloc] init]; + self.dataSource.backgroundView = background; __block BOOL executed = NO; [self.adapter reloadDataWithCompletion:^(BOOL finished) { - XCTAssertNil(self.adapter.emptyListView, @"Empty list view not correctly assigned"); + XCTAssertTrue(self.adapter.collectionView.backgroundView.hidden, @"Background view should be hidden"); + XCTAssertEqualObjects(background, self.adapter.collectionView.backgroundView, @"Background view not correctly assigned"); self.dataSource.objects = @[]; [self.adapter reloadDataWithCompletion:^(BOOL finished2) { - XCTAssertFalse(self.adapter.emptyListView.hidden, @"Empty list view should be visible"); - XCTAssertEqualObjects(emptyView, self.adapter.emptyListView, @"Empty list view not correctly assigned"); - executed = YES; - }]; - }]; - XCTAssertTrue(executed); -} - -- (void)test_whenDataSourceChanges_thatEmptyViewVisibilityChanges { - UIView *emptyView = [[UIView alloc] init]; - self.dataSource.emptyView = emptyView; - self.dataSource.objects = @[]; - [self.adapter reloadDataWithCompletion:nil]; - - self.dataSource.objects = @[@1]; - __block BOOL executed = NO; - [self.adapter reloadDataWithCompletion:^(BOOL finished) { - XCTAssertTrue(self.adapter.emptyListView.hidden, @"Empty list view should be hidden"); - XCTAssertEqualObjects(emptyView, self.adapter.emptyListView, @"Empty list view not correctly assigned"); - - self.dataSource.objects = @[]; - [self.adapter reloadDataWithCompletion:^(BOOL finished2) { - XCTAssertFalse(self.adapter.emptyListView.hidden, @"Empty list view should be visible"); - XCTAssertEqualObjects(emptyView, self.adapter.emptyListView, @"Empty list view not correctly assigned"); + XCTAssertFalse(self.adapter.collectionView.backgroundView.hidden, @"Background view should be visible"); + XCTAssertEqualObjects(background, self.adapter.collectionView.backgroundView, @"Background view not correctly assigned"); executed = YES; }]; }]; @@ -336,54 +315,54 @@ XCTAssertEqual(CGPointEqualToPoint(point, p), YES); \ - (void)test_whenDataSourceAddsItems_thatEmptyViewBecomesVisible { self.dataSource.objects = @[]; - UIView *emptyView = [UIView new]; - self.dataSource.emptyView = emptyView; + UIView *background = [UIView new]; + self.dataSource.backgroundView = background; [self.adapter reloadDataWithCompletion:nil]; - XCTAssertEqual(self.adapter.emptyListView, emptyView); - XCTAssertFalse(self.adapter.emptyListView.hidden); + XCTAssertEqual(self.collectionView.backgroundView, background); + XCTAssertFalse(self.collectionView.backgroundView.hidden); self.dataSource.objects = @[@2]; [self.adapter reloadDataWithCompletion:nil]; - XCTAssertTrue(self.adapter.emptyListView.hidden); + XCTAssertTrue(self.collectionView.backgroundView.hidden); } - (void)test_whenInsertingIntoEmptySection_thatEmptyViewBecomesHidden { self.dataSource.objects = @[@0]; - self.dataSource.emptyView = [UIView new]; + self.dataSource.backgroundView = [UIView new]; [self.adapter reloadDataWithCompletion:nil]; - XCTAssertFalse(self.adapter.emptyListView.hidden); + XCTAssertFalse(self.collectionView.backgroundView.hidden); IGListTestSection *sectionController = [self.adapter sectionControllerForObject:@(0)]; sectionController.items = 1; [self.adapter insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]]; - XCTAssertTrue(self.adapter.emptyListView.hidden); + XCTAssertTrue(self.collectionView.backgroundView.hidden); } - (void)test_whenDeletingAllItemsFromSection_thatEmptyViewBecomesVisible { self.dataSource.objects = @[@1]; - self.dataSource.emptyView = [UIView new]; + self.dataSource.backgroundView = [UIView new]; [self.adapter reloadDataWithCompletion:nil]; - XCTAssertNil(self.adapter.emptyListView); + XCTAssertTrue(self.collectionView.backgroundView.hidden); IGListTestSection *sectionController = [self.adapter sectionControllerForObject:@(1)]; sectionController.items = 0; [self.adapter deleteInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]]; - XCTAssertFalse(self.adapter.emptyListView.hidden); + XCTAssertFalse(self.collectionView.backgroundView.hidden); } - (void)test_whenEmptySectionAddsItems_thatEmptyViewBecomesHidden { self.dataSource.objects = @[@0]; - self.dataSource.emptyView = [UIView new]; + self.dataSource.backgroundView = [UIView new]; [self.adapter reloadDataWithCompletion:nil]; - XCTAssertFalse(self.adapter.emptyListView.hidden); + XCTAssertFalse(self.collectionView.backgroundView.hidden); IGListTestSection *sectionController = [self.adapter sectionControllerForObject:@(0)]; sectionController.items = 2; [self.adapter reloadSectionController:sectionController]; - XCTAssertTrue(self.adapter.emptyListView.hidden); + XCTAssertTrue(self.collectionView.backgroundView.hidden); } - (void)test_whenSectionItemsAreDeletedAsBatch_thatEmptyViewBecomesVisible { self.dataSource.objects = @[@1, @2]; - self.dataSource.emptyView = [UIView new]; + self.dataSource.backgroundView = [UIView new]; [self.adapter reloadDataWithCompletion:nil]; - XCTAssertNil(self.adapter.emptyListView); + XCTAssertTrue(self.collectionView.backgroundView.hidden); IGListTestSection *firstSectionController = [self.adapter sectionControllerForObject:@(1)]; IGListTestSection *secondSectionController = [self.adapter sectionControllerForObject:@(2)]; XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; @@ -394,7 +373,7 @@ XCTAssertEqual(CGPointEqualToPoint(point, p), YES); \ NSIndexSet *indexesToDelete = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]; [self.adapter deleteInSectionController:secondSectionController atIndexes:indexesToDelete]; } completion:^(BOOL finished) { - XCTAssertFalse(self.adapter.emptyListView.hidden); + XCTAssertFalse(self.collectionView.backgroundView.hidden); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:15 handler:nil]; diff --git a/Tests/Objects/IGListTestAdapterDataSource.h b/Tests/Objects/IGListTestAdapterDataSource.h index 0907c829..e873e975 100644 --- a/Tests/Objects/IGListTestAdapterDataSource.h +++ b/Tests/Objects/IGListTestAdapterDataSource.h @@ -16,6 +16,6 @@ // array of numbers which is then passed to -[IGListTestSection setItems:] @property (nonatomic, strong) NSArray *objects; -@property (nonatomic, strong) UIView *emptyView; +@property (nonatomic, strong) UIView *backgroundView; @end diff --git a/Tests/Objects/IGListTestAdapterDataSource.m b/Tests/Objects/IGListTestAdapterDataSource.m index cc574b50..19048871 100644 --- a/Tests/Objects/IGListTestAdapterDataSource.m +++ b/Tests/Objects/IGListTestAdapterDataSource.m @@ -25,7 +25,7 @@ } - (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter { - return self.emptyView; + return self.backgroundView; } @end