UICollectionViewLayout的功能為向UICollectionView提供布局信息威始,不僅包括cell的布局信息鼻吮,也包括追加視圖和裝飾視圖的布局信息车吹。
通常我們使用UICollectionViewLayout時是由系統(tǒng)幫助計算布局的,如圖:
這顯然不符合我們的需求醋闭。在系統(tǒng)的UICollectionViewLayout不能滿足我們的需求時窄驹,可實現(xiàn)一個自定義的繼承UICollectionViewLayout類CustomCollectionViewFlowLayout。
我們需要items優(yōu)先居左顯示证逻,并自動換行乐埠。如圖:
本文主要是講述CustomCollectionViewFlowLayout優(yōu)先居左排列的自定義布局。其實現(xiàn)的步驟如下:
首先自定義一個CustomCollectionViewFlowLayout繼承自UICollectionViewLayout類瑟曲。
#import <UIKit/UIKit.h>
@interface SkyLeftItemsCollectionViewFlowLayout : UICollectionViewFlowLayout<UICollectionViewDelegateFlowLayout>//繼承自UICollectionViewLayout類
@end
- UICollectionView的結(jié)構(gòu)
Cells
Supplementary Views 追加視圖 (類似Header或者Footer)
Decoration Views 裝飾視圖 (用作背景展示)
- UICollectionViewLayout是對collectionView的布局和行為進行描述的一個類。
在CustomCollectionViewFlowLayout.m中重載下列方法:
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
1.返回rect中的所有的元素的布局屬性
2.返回的是包含UICollectionViewLayoutAttributes的NSArray
3.UICollectionViewLayoutAttributes可以是cell豪治,追加視圖或裝飾視圖的信息洞拨,通過不同的UICollectionViewLayoutAttributes初始化方法可以得到不同類型的UICollectionViewLayoutAttributes:
1)layoutAttributesForCellWithIndexPath:
2)layoutAttributesForSupplementaryViewOfKind:withIndexPath:
3)layoutAttributesForDecorationViewOfKind:withIndexPath:
-(UICollectionViewLayoutAttributes )layoutAttributesForItemAtIndexPath:(NSIndexPath )indexPath
返回對應(yīng)于indexPath的位置的cell的布局屬性
-(UICollectionViewLayoutAttributes )layoutAttributesForSupplementaryViewOfKind:(NSString )kind atIndexPath:(NSIndexPath *)indexPath
返回對應(yīng)于indexPath的位置的追加視圖的布局屬性,如果沒有追加視圖可不重載
-(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString)decorationViewKind atIndexPath:(NSIndexPath )indexPath
返回對應(yīng)于indexPath的位置的裝飾視圖的布局屬性负拟,如果沒有裝飾視圖可不重載
UICollectionViewLayout中方法的調(diào)用順序:
- 1 -(void)prepareLayout 設(shè)置layout的結(jié)構(gòu)和初始需要的參數(shù)等烦衣。
- 2 -(CGSize) collectionViewContentSize 確定collectionView的所有內(nèi)容的尺寸。
- 3 -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect初始的layout的外觀將由該方法返回的UICollectionViewLayoutAttributes來決定掩浙。
- 4 在需要更新layout時花吟,需要給當(dāng)前l(fā)ayout發(fā)送
- 1 -invalidateLayout, 該消息會立即返回厨姚,并且預(yù)約在下一個loop的時候刷新當(dāng)前l(fā)ayout
- 2 -prepareLayout衅澈,
- 3 依次再調(diào)用-collectionViewContentSize和-layoutAttributesForElementsInRect來生成更新后的布局。
-
在- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect方法中谬墙,獲取系統(tǒng)計算好的Attributes
// 獲取系統(tǒng)計算好的Attributes
NSArray * attributesToReturn = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES];
-
在- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect方法中今布,遍歷系統(tǒng)的Attributes數(shù)組。
// 遍歷
for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) {
/*
typedef NS_ENUM(NSUInteger, UICollectionElementCategory) {
UICollectionElementCategoryCell,
UICollectionElementCategorySupplementaryView,
UICollectionElementCategoryDecorationView
};
*/
// representedElementKind == nil 時 representedElementCategory 為 UICollectionElementCategoryCell 即此時的attributes為item
if (nil == attributes.representedElementKind) {
NSIndexPath* indexPath = attributes.indexPath;
//對每個attributes的frame重新布局
attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
}
}
-
在- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 方法中拭抬,對每個Attributes進行重新布局部默。
//定義cell的布局
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
// 獲取系統(tǒng)計算好的當(dāng)前的Attributes
UICollectionViewLayoutAttributes * currentItemAttributes =
[[super layoutAttributesForItemAtIndexPath:indexPath] copy];
//設(shè)置內(nèi)邊距
UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout sectionInset];
//如果是第一個item。則其frame.origin.x直接在內(nèi)邊距的左邊造虎。重置currentItemAttributes的frame 返回currentItemAttributes
if (indexPath.item == 0) { // first item of section
CGRect frame = currentItemAttributes.frame;
frame.origin.x = sectionInset.left; // first item of the section should always be left aligned
currentItemAttributes.frame = frame;
//返回currentItemAttributes
return currentItemAttributes;
}
//如果不是第一個item傅蹂。則需要獲取前一個item的Attributes的frame屬性
NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
//前一個item與當(dāng)前的item的相鄰點
CGFloat previousFrameRightPoint = previousFrame.origin.x + previousFrame.size.width + kMaxCellSpacing;
//當(dāng)前的frame
CGRect currentFrame = currentItemAttributes.frame;
//
CGRect strecthedCurrentFrame = CGRectMake(0,
currentFrame.origin.y,
self.collectionView.frame.size.width,
currentFrame.size.height);
//判斷兩個結(jié)構(gòu)體是否有交錯.可以用CGRectIntersectsRect
//如果兩個結(jié)構(gòu)體沒有交錯,則表明這個item與前一個item不在同一行上算凿。則其frame.origin.x直接在內(nèi)邊距的左邊份蝴。重置currentItemAttributes的frame 返回currentItemAttributes
if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) {
// if current item is the first item on the line
// the approach here is to take the current frame, left align it to the edge of the view
// then stretch it the width of the collection view, if it intersects with the previous frame then that means it
// is on the same line, otherwise it is on it's own new line
CGRect frame = currentItemAttributes.frame;
frame.origin.x = sectionInset.left; // first item on the line should always be left aligned
currentItemAttributes.frame = frame;
//返回currentItemAttributes
return currentItemAttributes;
}
//如果如果兩個結(jié)構(gòu)體有交錯。將前一個item與當(dāng)前的item的相鄰點previousFrameRightPoint賦值給當(dāng)前的item的frame.origin.x
CGRect frame = currentItemAttributes.frame;
frame.origin.x = previousFrameRightPoint;
currentItemAttributes.frame = frame;
//返回currentItemAttributes
return currentItemAttributes;
}
其中氓轰,具體的布局方案:
1.獲取系統(tǒng)計算好的當(dāng)前的Attributes
2.判斷當(dāng)前的Attributes是否是第一個搞乏。如果是直接返回。
3.如果當(dāng)前的Attributes不是第一個Attributes戒努。則需要獲取前一個item的Attributes请敦。將兩個Attributes的frame是否有交錯镐躲。使用CGRectIntersectsRect對比。
4.如果兩個結(jié)構(gòu)體沒有交錯侍筛,則表明當(dāng)前的Attributes與前一個Attributes不在同一行上萤皂。則設(shè)置當(dāng)前的Attributes的frame在下一行的左邊,并返回當(dāng)前的Attributes匣椰。
5.如果如果兩個結(jié)構(gòu)體有交錯裆熙。則表明當(dāng)前的Attributes與前一個Attributes在同一行上。則設(shè)置當(dāng)前的Attributes的frame在前一個Attributes的左邊禽笑,并返回當(dāng)前的Attributes入录。
出現(xiàn)警告:This is likely occurring because the flow layout subclass SkyLeftItemsCollectionViewFlowLayout is modifying attributes returned by UICollectionViewFlowLayout without copying them的解決方法
- 如果是獲取系統(tǒng)計算好的Attributes時使用
NSMutableArray *attributesToReturn = [[super layoutAttributesForElementsInRect:rect] mutableCopy]方法的話會有警告:
This is likely occurring because the flow layout subclass SkyLeftItemsCollectionViewFlowLayout is modifying attributes returned by UICollectionViewFlowLayout without copying them
- 其解決方法就是在獲取系統(tǒng)計算好的Attributes時進行copyItems。
//定義屏幕展示的范圍和數(shù)量
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
// 獲取系統(tǒng)計算好的Attributes
NSArray * attributesToReturn = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES];//copyItems將系統(tǒng)的Attributes進行拷貝
/*
也可以這樣寫
NSArray* arrayattributesToReturn = [super layoutAttributesForElementsInRect:rect];
NSArray * attributesToReturn = [[NSArray alloc] initWithArray:arrayattributesToReturn copyItems:YES];
*/
- 上述方法添加之后佳镜,運行還是有同樣的警告僚稿。我們還需對每個Attributes布局時進行copy。
//定義cell的布局
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
// 獲取系統(tǒng)計算好的當(dāng)前的Attributes
UICollectionViewLayoutAttributes * currentItemAttributes =
[[super layoutAttributesForItemAtIndexPath:indexPath] copy];
}