mirror of
https://github.com/Instagram/IGListKit
synced 2026-05-24 09:48:21 +00:00
Show header when section item is empty
Summary: Issue fixed: #1117 I adding a new constructor for making a `IGListCollectionViewLayout` instance that can always show sticky header although section data is empty. It's working well and [this is demo project](https://github.com/marcuswu0814/IGListKit_ShowStickyHeaderWhenDataEmpty). Bug I'm not sure is any good way to let this much more readability. Is there any good advice? ```objc const CGRect headerBounds = (self.scrollDirection == UICollectionViewScrollDirectionVertical) ? CGRectMake(insets.left, (itemCount == 0) ? CGRectGetMaxY(rollingSectionBounds) : CGRectGetMinY(rollingSectionBounds) - headerSize.height, paddedLengthInFixedDirection, headerSize.height) : CGRectMake((itemCount == 0) ? CGRectGetMaxX(rollingSectionBounds) : CGRectGetMinX(rollingSectionBounds) - headerSize.width, insets.top, headerSize.width, paddedLengthInFixedDirection); ``` - [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/1129 Differential Revision: D7551628 Pulled By: rnystrom fbshipit-source-id: a60b65a92efcea5175c86aaed1de02686ea6d20a
This commit is contained in:
parent
9315299fbc
commit
e49c94b25d
4 changed files with 107 additions and 25 deletions
|
|
@ -11,6 +11,8 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instag
|
|||
|
||||
- 5x improvement to diffing performance when result is only inserts or deletes. [Ryan Nystrom](https://github.com/rnystrom) [(tbd)](tbd)
|
||||
|
||||
- Can always show sticky header although section data is empty. [Marcus Wu](https://github.com/marcuswu0814) [(#1129)](https://github.com/Instagram/IGListKit/pull/1129)
|
||||
|
||||
- Added `-[IGListCollectionContext dequeueReusableCellOfClass:withReuseIdentifier:forSectionController:atIndex:]` to allow for registering cells of the same class with different reuse identifiers. [Jeremy Lawrence](https://github.com/Ziewvater) (tbd)
|
||||
|
||||
### Fixes
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ NS_SWIFT_NAME(ListCollectionViewLayout)
|
|||
*/
|
||||
@property (nonatomic, assign) CGFloat stickyHeaderYOffset;
|
||||
|
||||
/**
|
||||
Set this to `YES` to show sticky header when a section had no item. Default is `NO`.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL showHeaderWhenEmpty;
|
||||
|
||||
/**
|
||||
Notify the layout that a specific section was modified before invalidation. Used to optimize layout re-calculation.
|
||||
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ static void adjustZIndexForAttributes(UICollectionViewLayoutAttributes *attribut
|
|||
const NSInteger itemCount = _sectionData[section].itemBounds.size();
|
||||
|
||||
// do not add headers if there are no items
|
||||
if (itemCount > 0) {
|
||||
if (itemCount > 0 || self.showHeaderWhenEmpty) {
|
||||
for (NSString *elementKind in _supplementaryAttributesCache.allKeys) {
|
||||
NSIndexPath *indexPath = indexPathForSection(section);
|
||||
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:elementKind
|
||||
|
|
@ -319,7 +319,6 @@ static void adjustZIndexForAttributes(UICollectionViewLayoutAttributes *attribut
|
|||
|
||||
if ([elementKind isEqualToString:UICollectionElementKindSectionHeader]) {
|
||||
frame = entry.headerBounds;
|
||||
|
||||
|
||||
if (self.stickyHeaders) {
|
||||
CGFloat offset = CGPointGetCoordinateInDirection(collectionView.contentOffset, self.scrollDirection) + self.topContentInset + self.stickyHeaderYOffset;
|
||||
|
|
@ -484,6 +483,8 @@ static void adjustZIndexForAttributes(UICollectionViewLayoutAttributes *attribut
|
|||
|
||||
for (NSInteger section = _minimumInvalidatedSection; section < sectionCount; section++) {
|
||||
const NSInteger itemCount = [dataSource collectionView:collectionView numberOfItemsInSection:section];
|
||||
const BOOL itemsEmpty = itemCount == 0;
|
||||
const BOOL hideHeaderWhenItemsEmpty = itemsEmpty && !self.showHeaderWhenEmpty;
|
||||
_sectionData[section].itemBounds = std::vector<CGRect>(itemCount);
|
||||
|
||||
const CGSize headerSize = [delegate collectionView:collectionView layout:self referenceSizeForHeaderInSection:section];
|
||||
|
|
@ -495,8 +496,8 @@ static void adjustZIndexForAttributes(UICollectionViewLayoutAttributes *attribut
|
|||
const CGSize paddedCollectionViewSize = UIEdgeInsetsInsetRect(contentInsetAdjustedCollectionViewBounds, insets).size;
|
||||
const UICollectionViewScrollDirection fixedDirection = self.scrollDirection == UICollectionViewScrollDirectionHorizontal ? UICollectionViewScrollDirectionVertical : UICollectionViewScrollDirectionHorizontal;
|
||||
const CGFloat paddedLengthInFixedDirection = CGSizeGetLengthInDirection(paddedCollectionViewSize, fixedDirection);
|
||||
const CGFloat headerLengthInScrollDirection = CGSizeGetLengthInDirection(headerSize, self.scrollDirection);
|
||||
const CGFloat footerLengthInScrollDirection = CGSizeGetLengthInDirection(footerSize, self.scrollDirection);
|
||||
const CGFloat headerLengthInScrollDirection = hideHeaderWhenItemsEmpty ? 0 : CGSizeGetLengthInDirection(headerSize, self.scrollDirection);
|
||||
const CGFloat footerLengthInScrollDirection = hideHeaderWhenItemsEmpty ? 0 : CGSizeGetLengthInDirection(footerSize, self.scrollDirection);
|
||||
const BOOL headerExists = headerLengthInScrollDirection > 0;
|
||||
const BOOL footerExists = footerLengthInScrollDirection > 0;
|
||||
|
||||
|
|
@ -577,29 +578,33 @@ static void adjustZIndexForAttributes(UICollectionViewLayoutAttributes *attribut
|
|||
rollingSectionBounds = CGRectUnion(rollingSectionBounds, frame);
|
||||
}
|
||||
}
|
||||
|
||||
const CGRect headerBounds = (self.scrollDirection == UICollectionViewScrollDirectionVertical) ?
|
||||
CGRectMake(insets.left,
|
||||
CGRectGetMinY(rollingSectionBounds) - headerSize.height,
|
||||
paddedLengthInFixedDirection,
|
||||
headerSize.height) :
|
||||
CGRectMake(CGRectGetMinX(rollingSectionBounds) - headerSize.width,
|
||||
insets.top,
|
||||
headerSize.width,
|
||||
paddedLengthInFixedDirection);
|
||||
|
||||
|
||||
const CGRect headerBounds = self.scrollDirection == UICollectionViewScrollDirectionVertical ?
|
||||
CGRectMake(insets.left,
|
||||
itemsEmpty ? CGRectGetMaxY(rollingSectionBounds) : CGRectGetMinY(rollingSectionBounds) - headerSize.height,
|
||||
paddedLengthInFixedDirection,
|
||||
hideHeaderWhenItemsEmpty ? 0 : headerSize.height) :
|
||||
CGRectMake(itemsEmpty ? CGRectGetMaxX(rollingSectionBounds) : CGRectGetMinX(rollingSectionBounds) - headerSize.width,
|
||||
insets.top,
|
||||
hideHeaderWhenItemsEmpty ? 0 : headerSize.width,
|
||||
paddedLengthInFixedDirection);
|
||||
|
||||
_sectionData[section].headerBounds = headerBounds;
|
||||
|
||||
|
||||
if (itemsEmpty) {
|
||||
rollingSectionBounds = headerBounds;
|
||||
}
|
||||
|
||||
const CGRect footerBounds = (self.scrollDirection == UICollectionViewScrollDirectionVertical) ?
|
||||
CGRectMake(insets.left,
|
||||
CGRectGetMaxY(rollingSectionBounds),
|
||||
paddedLengthInFixedDirection,
|
||||
footerSize.height) :
|
||||
CGRectMake(CGRectGetMaxX(rollingSectionBounds) + insets.right,
|
||||
insets.top,
|
||||
footerSize.width,
|
||||
paddedLengthInFixedDirection);
|
||||
|
||||
CGRectMake(insets.left,
|
||||
CGRectGetMaxY(rollingSectionBounds),
|
||||
paddedLengthInFixedDirection,
|
||||
hideHeaderWhenItemsEmpty ? 0 : footerSize.height) :
|
||||
CGRectMake(CGRectGetMaxX(rollingSectionBounds) + insets.right,
|
||||
insets.top,
|
||||
hideHeaderWhenItemsEmpty ? 0 : footerSize.width,
|
||||
paddedLengthInFixedDirection);
|
||||
|
||||
_sectionData[section].footerBounds = footerBounds;
|
||||
|
||||
// union the header before setting the bounds of the section
|
||||
|
|
|
|||
|
|
@ -41,6 +41,12 @@ static const CGRect kTestFrame = (CGRect){{0, 0}, {100, 100}};
|
|||
return [self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter atIndexPath:genIndexPath(section, 0)];
|
||||
}
|
||||
|
||||
- (void)setUpWithStickyHeaders:(BOOL)sticky showHeaderWhenEmpty:(BOOL)showHeaderWhenEmpty {
|
||||
self.layout = [[IGListCollectionViewLayout alloc] initWithStickyHeaders:YES topContentInset:0 stretchToEdge:NO];
|
||||
self.layout.showHeaderWhenEmpty = showHeaderWhenEmpty;
|
||||
[self setUpCollectionViewAndDataSource:kTestFrame];
|
||||
}
|
||||
|
||||
- (void)setUpWithStickyHeaders:(BOOL)sticky topInset:(CGFloat)inset {
|
||||
[self setUpWithStickyHeaders:sticky topInset:inset stretchToEdge:NO];
|
||||
}
|
||||
|
|
@ -55,6 +61,10 @@ static const CGRect kTestFrame = (CGRect){{0, 0}, {100, 100}};
|
|||
|
||||
- (void)setUpWithStickyHeaders:(BOOL)sticky scrollDirection:(UICollectionViewScrollDirection)scrollDirection topInset:(CGFloat)inset stretchToEdge:(BOOL)stretchToEdge testFrame:(CGRect)testFrame {
|
||||
self.layout = [[IGListCollectionViewLayout alloc] initWithStickyHeaders:sticky scrollDirection:scrollDirection topContentInset:inset stretchToEdge:stretchToEdge];
|
||||
[self setUpCollectionViewAndDataSource:testFrame];
|
||||
}
|
||||
|
||||
- (void)setUpCollectionViewAndDataSource:(CGRect)testFrame {
|
||||
self.dataSource = [IGLayoutTestDataSource new];
|
||||
self.collectionView = [[UICollectionView alloc] initWithFrame:testFrame collectionViewLayout:self.layout];
|
||||
self.collectionView.dataSource = self.dataSource;
|
||||
|
|
@ -86,6 +96,66 @@ static const CGRect kTestFrame = (CGRect){{0, 0}, {100, 100}};
|
|||
XCTAssertTrue(CGSizeEqualToSize(CGSizeZero, self.collectionView.contentSize));
|
||||
}
|
||||
|
||||
- (void)test_whenSectionDataIsEmpty_thatStickyHeaderStillShow {
|
||||
[self setUpWithStickyHeaders:YES showHeaderWhenEmpty:YES];
|
||||
|
||||
[self prepareWithData:@[[[IGLayoutTestSection alloc] initWithInsets:UIEdgeInsetsZero
|
||||
lineSpacing:0
|
||||
interitemSpacing:0
|
||||
headerHeight:10
|
||||
footerHeight:0
|
||||
items:nil],
|
||||
[[IGLayoutTestSection alloc] initWithInsets:UIEdgeInsetsZero
|
||||
lineSpacing:0
|
||||
interitemSpacing:0
|
||||
headerHeight:20
|
||||
footerHeight:0
|
||||
items:nil],
|
||||
[[IGLayoutTestSection alloc] initWithInsets:UIEdgeInsetsZero
|
||||
lineSpacing:0
|
||||
interitemSpacing:0
|
||||
headerHeight:30
|
||||
footerHeight:0
|
||||
items:nil]]];
|
||||
|
||||
IGAssertEqualFrame([self headerForSection:0].frame, 0, 0, 100, 10);
|
||||
IGAssertEqualFrame([self headerForSection:1].frame, 0, 10, 100, 20);
|
||||
IGAssertEqualFrame([self headerForSection:2].frame, 0, 30, 100, 30);
|
||||
}
|
||||
|
||||
- (void)test_whenSectionDataIsEmpty_thatStickyHeaderShouldBeHidden {
|
||||
[self setUpWithStickyHeaders:YES showHeaderWhenEmpty:NO];
|
||||
|
||||
[self prepareWithData:@[[[IGLayoutTestSection alloc] initWithInsets:UIEdgeInsetsZero
|
||||
lineSpacing:0
|
||||
interitemSpacing:0
|
||||
headerHeight:10
|
||||
footerHeight:0
|
||||
items:@[
|
||||
[[IGLayoutTestItem alloc] initWithSize:(CGSize) {85, 10}]
|
||||
]],
|
||||
[[IGLayoutTestSection alloc] initWithInsets:UIEdgeInsetsZero
|
||||
lineSpacing:0
|
||||
interitemSpacing:0
|
||||
headerHeight:20
|
||||
footerHeight:0
|
||||
items:nil],
|
||||
[[IGLayoutTestSection alloc] initWithInsets:UIEdgeInsetsZero
|
||||
lineSpacing:0
|
||||
interitemSpacing:0
|
||||
headerHeight:20
|
||||
footerHeight:0
|
||||
items:@[
|
||||
[[IGLayoutTestItem alloc] initWithSize:(CGSize) {85, 10}],
|
||||
[[IGLayoutTestItem alloc] initWithSize:(CGSize) {85, 20}],
|
||||
]]
|
||||
]];
|
||||
|
||||
IGAssertEqualFrame([self headerForSection:0].frame, 0, 0, 100, 10);
|
||||
IGAssertEqualFrame([self headerForSection:1].frame, 0, 0, 0, 0);
|
||||
IGAssertEqualFrame([self headerForSection:2].frame, 0, 20, 100, 20);
|
||||
}
|
||||
|
||||
- (void)test_whenLayingOutCellsVertically_withHeaderHeight_withLineSpacing_withInsets_thatFramesCorrect {
|
||||
[self setUpWithStickyHeaders:NO topInset:0];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue