UITableView系統(tǒng)提供了兩種常用樣式(UITableViewStylePlain和UITableViewStyleGrouped),UITableViewStylePlain可以讓區(qū)頭停留在頂部,但是UICollectionView沒有直接提供這樣的樣式苍糠,如果需要我們只能重寫UICollectionViewFlowLayout,直接上代碼:
//
// UICollectionPlainFlowLayout.h
// FlowLayoutDemo
//
// Created by IDBENY on 2017/7/19.
// Copyright ? 2017年 www.idbeny.com All rights reserved.
//
#import <UIKit/UIKit.h>
@interface ShoppingCollectionPlainFlowLayout : UICollectionViewFlowLayout
@property (nonatomic, assign) CGFloat navHeight;//默認為64.0, default is 64.0
@end
//
// UICollectionPlainFlowLayout.m
// FlowLayoutDemo
//
// Created by IDBENY on 2017/7/19.
// Copyright ? 2017年 www.idbeny.com All rights reserved.
//
#import "ShoppingCollectionPlainFlowLayout.h"
@implementation ShoppingCollectionPlainFlowLayout
- (instancetype)init {
self = [super init];
if (self) {
_navHeight = 64.0;
}
return self;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
//UICollectionViewLayoutAttributes:我稱它為collectionView中的item(包括cell和header辰企、footer這些)的《結構信息》
//截取到父類所返回的數(shù)組(里面放的是當前屏幕所能展示的item的結構信息)宠能,并轉化成不可變數(shù)組
NSMutableArray *superArray = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
//創(chuàng)建存索引的數(shù)組夸楣,無符號(正整數(shù))椰苟,無序(不能通過下標取值)锅知,不可重復(重復的話會自動過濾)
NSMutableIndexSet *noneHeaderSections = [NSMutableIndexSet indexSet];
//遍歷superArray氯材,得到一個當前屏幕中所有的section數(shù)組
for (UICollectionViewLayoutAttributes *attributes in superArray) {
//如果當前的元素分類是一個cell站绪,將cell所在的分區(qū)section加入數(shù)組牡昆,重復的話會自動過濾
if (attributes.representedElementCategory == UICollectionElementCategoryCell) {
[noneHeaderSections addIndex:attributes.indexPath.section];
}
}
//遍歷superArray爱致,將當前屏幕中擁有的header的section從數(shù)組中移除愤估,得到一個當前屏幕中沒有header的section數(shù)組
//正常情況下畅哑,隨著手指往上移旭绒,header脫離屏幕會被系統(tǒng)回收而cell尚在仇轻,也會觸發(fā)該方法
for (UICollectionViewLayoutAttributes *attributes in superArray) {
//如果當前的元素是一個header京痢,將header所在的section從數(shù)組中移除
if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
[noneHeaderSections removeIndex:attributes.indexPath.section];
}
}
//遍歷當前屏幕中沒有header的section數(shù)組
[noneHeaderSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
//取到當前section中第一個item的indexPath
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
//獲取當前section在正常情況下已經離開屏幕的header結構信息
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
//如果當前分區(qū)確實有因為離開屏幕而被系統(tǒng)回收的header
if (attributes) {
//將該header結構信息重新加入到superArray中去
[superArray addObject:attributes];
}
}];
//遍歷superArray,改變header結構信息中的參數(shù)篷店,使它可以在當前section還沒完全離開屏幕的時候一直顯示
for (UICollectionViewLayoutAttributes *attributes in superArray) {
//如果當前item是header
if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
//得到當前header所在分區(qū)的cell的數(shù)量
NSInteger numberOfItemsInSection = [self.collectionView numberOfItemsInSection:attributes.indexPath.section];
//得到第一個item的indexPath
NSIndexPath *firstItemIndexPath = [NSIndexPath indexPathForItem:0 inSection:attributes.indexPath.section];
//得到最后一個item的indexPath
NSIndexPath *lastItemIndexPath = [NSIndexPath indexPathForItem:MAX(0, numberOfItemsInSection-1) inSection:attributes.indexPath.section];
//得到第一個item和最后一個item的結構信息
UICollectionViewLayoutAttributes *firstItemAttributes, *lastItemAttributes;
if (numberOfItemsInSection>0) {
//cell有值祭椰,則獲取第一個cell和最后一個cell的結構信息
firstItemAttributes = [self layoutAttributesForItemAtIndexPath:firstItemIndexPath];
lastItemAttributes = [self layoutAttributesForItemAtIndexPath:lastItemIndexPath];
} else {
//cell沒值,就新建一個UICollectionViewLayoutAttributes
firstItemAttributes = [UICollectionViewLayoutAttributes new];
//然后模擬出在當前分區(qū)中的唯一一個cell,cell在header的下面疲陕,高度為0方淤,還與header隔著可能存在的sectionInset的top
CGFloat y = CGRectGetMaxY(attributes.frame)+self.sectionInset.top;
firstItemAttributes.frame = CGRectMake(0, y, 0, 0);
//因為只有一個cell,所以最后一個cell等于第一個cell
lastItemAttributes = firstItemAttributes;
}
//獲取當前header的frame
CGRect rect = attributes.frame;
//當前的滑動距離 + 因為導航欄產生的偏移量蹄殃,默認為64(如果app需求不同携茂,需自己設置)
CGFloat offset = self.collectionView.contentOffset.y + _navHeight;
//第一個cell的y值 - 當前header的高度 - 可能存在的sectionInset的top
CGFloat headerY = firstItemAttributes.frame.origin.y - rect.size.height - self.sectionInset.top;
//哪個大取哪個,保證header懸停
//針對當前header基本上都是offset更加大诅岩,針對下一個header則會是headerY大讳苦,各自處理
CGFloat maxY = MAX(offset,headerY);
//最后一個cell的y值 + 最后一個cell的高度 + 可能存在的sectionInset的bottom - 當前header的高度
//當當前section的footer或者下一個section的header接觸到當前header的底部带膜,計算出的headerMissingY即為有效值
CGFloat headerMissingY = CGRectGetMaxY(lastItemAttributes.frame) + self.sectionInset.bottom - rect.size.height;
//給rect的y賦新值,因為在最后消失的臨界點要跟誰消失鸳谜,所以取小
rect.origin.y = MIN(maxY,headerMissingY);
//給header的結構信息的frame重新賦值
attributes.frame = rect;
//如果按照正常情況下,header離開屏幕被系統(tǒng)回收膝藕,而header的層次關系又與cell相等,如果不去理會咐扭,會出現(xiàn)cell在header上面的情況
//通過打印可以知道cell的層次關系zIndex數(shù)值為0芭挽,我們可以將header的zIndex設置成1,如果不放心草描,也可以將它設置成非常大览绿,這里隨便填了個7
attributes.zIndex = 7;
}
}
//轉換回不可變數(shù)組,并返回
return [superArray copy];
}
//return YES;表示一旦滑動就實時調用上面這個layoutAttributesForElementsInRect:方法
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
return YES;
}
@end