I need to create a UICollectionView
with cells of different sizes (1x1, 2x2, 2x1, 3x2.5). I\'ve already code to add cells depending on which size they are using
Here it's the same logic as Larme suggested. But with less hardcode and which give you the possibility of setting the number of items you want without adding new case:
.
I call "pattern" a set of 5 items. So first I define constant values:
#define MAX_COLUMN 3 // Max columns in the pattern
#define MAX_LINE_PER_PATTERN 3 // Max lines in the pattern
#define PATTERN_ITEM_COUNT 5 // Max items in the pattern
Then I create a custom layout with 2 properties NSMutableArray *layoutAttributes
and CGFloat contentHeight
in which I need to override the methods:
- (void)prepareLayout{
[super prepareLayout];
if (!self.layoutAttributes){
self.layoutAttributes = [[NSMutableArray alloc] init];
CGFloat cellWidth = self.collectionView.frame.size.width / MAX_COLUMN;
CGFloat cellHeight = cellWidth;
self.contentHeight = 0.f;
for (int item = 0 ; item < [self.collectionView numberOfItemsInSection:0] ; item ++){
CGFloat width, height = 0.f;
CGFloat xPos, yPos = 0.f;
NSInteger patternCount = (NSInteger)((CGFloat)item / (CGFloat)PATTERN_ITEM_COUNT);
NSInteger currentIndex = item % PATTERN_ITEM_COUNT;
switch (currentIndex) {
case 0:
{
xPos = 0.f;
yPos = 0.f + MAX_LINE_PER_PATTERN * cellHeight * patternCount;
width = cellWidth;
height = cellHeight;
self.contentHeight += cellHeight;
break;
}
case 1:
{
xPos = cellWidth;
yPos = 0.f + MAX_LINE_PER_PATTERN * cellHeight * patternCount;
width = cellWidth * 2.f;
height = cellHeight * 2.f;
self.contentHeight += cellHeight;
break;
}
case 2:
{
xPos = 0.f;
yPos = cellHeight + MAX_LINE_PER_PATTERN * cellHeight * patternCount;
width = cellWidth;
height = cellHeight;
break;
}
case 3:
{
xPos = 0.f;
yPos = 2.f * cellHeight + MAX_LINE_PER_PATTERN * cellHeight * patternCount;
width = cellWidth * 2.f;
height = cellHeight;
self.contentHeight += cellHeight;
break;
}
case 4:
{
xPos = 2.f * cellWidth;
yPos = 2.f * cellHeight + MAX_LINE_PER_PATTERN * cellHeight * patternCount;
width = cellWidth;
height = cellHeight;
break;
}
default:
NSLog(@"error with index");
break;
}
UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForRow:item inSection:0]];
attr.frame = CGRectMake(xPos,
yPos,
width,
height);
[self.layoutAttributes addObject:attr];
}
}
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray *currentAttributes = [NSMutableArray new];
for (UICollectionViewLayoutAttributes *attr in self.layoutAttributes) {
if (CGRectIntersectsRect(attr.frame, rect))
{
[currentAttributes addObject:attr];
}
}
return currentAttributes;
}
- (CGSize)collectionViewContentSize{
return CGSizeMake(self.collectionView.frame.size.width, self.contentHeight);
}
Then assign this custom layout to self.collectionView.collectionViewLayout
and that's it. You can find more information and swift version here.
A sample example by subclassing UICollectionViewLayout. All values are hard coded, just to explicit the logic behind it. Of course, that could be optimized.
@interface CustomCollectionViewLayout ()
@property (nonatomic, strong) NSMutableDictionary *cellLayouts;
@property (nonatomic, assign) CGSize unitSize;
@end
@implementation CustomCollectionViewLayout
-(id)initWithSize:(CGSize)size
{
self = [super init];
if (self)
{
_unitSize = CGSizeMake(size.width/3,150);
_cellLayouts = [[NSMutableDictionary alloc] init];
}
return self;
}
-(void)prepareLayout
{
for (NSInteger i = 0; i < [[self collectionView] numberOfItemsInSection:0]; i ++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGRect frame;
switch ([indexPath item])
{
case 0:
frame = CGRectMake(0, 0, _unitSize.width*3, _unitSize.height*2.5);
break;
case 1:
frame = CGRectMake(0, _unitSize.height*2.5, _unitSize.width, _unitSize.height);
break;
case 2:
frame = CGRectMake(_unitSize.width, _unitSize.height*2.5, _unitSize.width*2, _unitSize.height*2);
break;
case 3:
frame = CGRectMake(0, _unitSize.height*2.5+_unitSize.height, _unitSize.width, _unitSize.height);
break;
case 4:
frame = CGRectMake(0, _unitSize.height*2.5+_unitSize.height+_unitSize.height, _unitSize.width*2, _unitSize.height);
break;
case 5:
frame = CGRectMake(_unitSize.width*2, _unitSize.height*2.5+_unitSize.height+_unitSize.height, _unitSize.width, _unitSize.height);
break;
case 6:
frame = CGRectMake(0, _unitSize.height*2.5+_unitSize.height+_unitSize.height+_unitSize.height, _unitSize.width, _unitSize.height);
break;
case 7:
frame = CGRectMake(_unitSize.width, _unitSize.height*2.5+_unitSize.height+_unitSize.height+_unitSize.height, _unitSize.width*2, _unitSize.height);
break;
case 8:
frame = CGRectMake(0, _unitSize.height*2.5+_unitSize.height+_unitSize.height+_unitSize.height+_unitSize.height, _unitSize.width*3, _unitSize.height*2.5);
break;
default:
frame = CGRectZero;
break;
}
[attributes setFrame:frame];
[[self cellLayouts] setObject:attributes forKey:indexPath];
}
}
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *retAttributes = [[NSMutableArray alloc] init];
for (NSIndexPath *anIndexPath in [self cellLayouts])
{
UICollectionViewLayoutAttributes *attributes = [self cellLayouts][anIndexPath];
if (CGRectIntersectsRect(rect, [attributes frame]))
{
[retAttributes addObject:attributes];
}
}
return retAttributes;
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
return [self cellLayouts][indexPath];
}
-(CGSize)collectionViewContentSize
{
return CGSizeMake(_unitSize.width*3, _unitSize.height*9);
}
@end
Then, you just have to call :
CustomCollectionViewLayout *layout = [[CustomCollectionViewLayout alloc] initWithSize:self.myCollectionView.bounds.frame.size];
[self.myCollectionView setCollectionViewLayout:layout];
Rendering :