Cells Order in Uicollectionview

iOS UICollectionView special cell order

SOLUTION: I've created a subclass of UICollectionView named "CenteringCollectionView" and with a few calculations of the sections I made it!

The .h file:

#import <UIKit/UIKit.h>

@class CenteringCollectionView;
@protocol CenteringCollectionViewDelegate <NSObject>

-(void)collectionView:(CenteringCollectionView *)collectionView didDequeueReusableCell:(UICollectionViewCell *)cell indexPath:(NSIndexPath *)indexPath;

@end

@interface CenteringCollectionView : UICollectionView

@property (nonatomic, strong) NSMutableArray *dataSourceArr;
@property (nonatomic, weak) id<CenteringCollectionViewDelegate> delegateCenteringCollection;

@end

The .m file:

#import "CenteringCollectionView.h"

@interface CenteringCollectionView () <UICollectionViewDelegate, UICollectionViewDataSource>

@property (nonatomic, assign) IBInspectable long elementsInRow;
@property (nonatomic, assign) long elementsInRowInitialValue;
@property (nonatomic) IBInspectable NSString *cellIdentifier;
@property (nonatomic) IBInspectable CGFloat cellRelativeSize; // 0..1
@property (nonatomic, assign) long numOfSections;
@property (nonatomic, assign) IBInspectable BOOL autoResize; // *** If we want auto resize - we need to set the height constraint of the collection view in size of 1 line only even if we have more than 1 line (section).
@property (nonatomic, assign)IBInspectable CGFloat heightMiddleSpacing;
@property (nonatomic, assign) long cellSize;
//@property (nonatomic, assign) CGFloat verticalTopInset;
@property (nonatomic, assign) CGFloat initialHeightConstraint;
@property (nonatomic, weak) NSLayoutConstraint *selfHeightConstraint;
@property (nonatomic, assign) CGFloat cellSpacing;
@property (nonatomic, assign) BOOL shouldReloadUIElements;

// UI IBInspectable

@property (nonatomic, weak) IBInspectable UIColor *runtimeColor;

@end

static long const maxElementsInRowDefault = 3;

@implementation CenteringCollectionView

-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
self.elementsInRow = maxElementsInRowDefault; // will get the default value if not stored value in storyboard
self.elementsInRowInitialValue = self.elementsInRow;
self.cellRelativeSize = 0.5;
self.initialHeightConstraint = -1;
}
return self;
}

-(void)setDataSourceCount:(long)dataSourceCount
{
if (dataSourceCount == _dataSourceCount)
{
return;
}

_dataSourceCount = dataSourceCount;
self.shouldReloadUIElements = YES;
self.elementsInRow = MIN(self.elementsInRowInitialValue, self.dataSourceCount);
self.numOfSections = ceil((CGFloat)self.dataSourceCount / (CGFloat)self.elementsInRow);

CGFloat selfHeight = [self handleAutoResizeAndReturnTheNewHeightIfNeeded];
CGFloat selfWidth = CGRectGetWidth(self.frame);

CGFloat cellWidth = (selfWidth / self.elementsInRow) * self.cellRelativeSize;
CGFloat cellHeight = (selfHeight / self.numOfSections) * self.cellRelativeSize;

self.cellSize = MIN(cellWidth, cellHeight);

dispatch_async(dispatch_get_main_queue(), ^{

[self setCollectionView];
[self reloadData];
});
}

-(void)awakeFromNib
{
[super awakeFromNib];
self.elementsInRowInitialValue = self.elementsInRow;
[self handleUIelementsIBInspectable];
}

-(void)handleUIelementsIBInspectable
{
if (self.runtimeColor)
{
[self setBackgroundColor:self.runtimeColor];
}
}

-(CGFloat)handleAutoResizeAndReturnTheNewHeightIfNeeded
{
if (self.autoResize)
{
for (NSLayoutConstraint *constraint in [self constraints])
{
if (constraint.firstAttribute == NSLayoutAttributeHeight)
{
if (self.initialHeightConstraint == -1) // not set yet
{
self.initialHeightConstraint = constraint.constant;
}

if (!self.selfHeightConstraint)
{
self.selfHeightConstraint = constraint;
}

CGFloat newHeight = self.initialHeightConstraint * self.numOfSections;
constraint.constant = newHeight;

if (self.bounds.size.height != newHeight)
{
CGRect frame = self.bounds;
frame.size.height = newHeight;
[self setBounds:frame];
}

dispatch_async(dispatch_get_main_queue(), ^{
[self.superview layoutIfNeeded];
[self layoutIfNeeded];
});

return newHeight;
}
}
}

return CGRectGetHeight(self.frame);
}

-(long)numOfSpacesInRow
{
return self.elementsInRow + 1;
}

-(long)numOfSpacesBetweenLines
{
return self.numOfSections + 1;
}

-(void)setCellRelativeSize:(CGFloat)cellRelativeSize
{
_cellRelativeSize = MAX(0, MIN(cellRelativeSize, 1));
}

-(void)setCollectionView
{
[self reloadData];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
CGFloat horizontalCellSpacing = ((CGRectGetWidth(self.frame) - (self.cellSize * self.elementsInRow)) / self.numOfSpacesInRow);
CGFloat verticalCellSpacing = (CGRectGetHeight(self.frame) - (self.numOfSections * self.cellSize)) / self.numOfSpacesBetweenLines;

self.cellSpacing = MAX(MIN(horizontalCellSpacing, verticalCellSpacing), 0);
[layout setMinimumInteritemSpacing:self.cellSpacing];
[layout setMinimumLineSpacing:self.cellSpacing];
[layout setScrollDirection:UICollectionViewScrollDirectionVertical];
[self setCollectionViewLayout:layout];

self.showsVerticalScrollIndicator = NO;
self.showsHorizontalScrollIndicator = NO;
self.scrollEnabled = NO;

if (!self.delegate)
{
self.delegate = self;
self.dataSource = self;
}
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(self.cellSize, self.cellSize);
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
BOOL isLastSection = (section == self.numOfSections - 1);
if (isLastSection == NO)
{
return self.elementsInRow;
}
else
{
long numOfLeftItemsInLastRow = self.dataSourceCount % self.elementsInRow;
if (numOfLeftItemsInLastRow == 0)
{
return self.elementsInRow;
}
else
{
return numOfLeftItemsInLastRow;
}
}
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:self.cellIdentifier forIndexPath:indexPath];
if ([self.delegateCenteringCollection respondsToSelector:@selector(collectionView:didDequeueReusableCell:indexPath:)])
{
[self.delegateCenteringCollection collectionView:self didDequeueReusableCell:cell indexPath:[self indexPathWithoutSectionsFrom:indexPath]];
}
return cell;
}

-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegateCenteringCollection respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:cell:)])
{
UICollectionViewCell *selectedCell = [collectionView cellForItemAtIndexPath:indexPath];
[self.delegateCenteringCollection collectionView:self didSelectItemAtIndexPath:[self indexPathWithoutSectionsFrom:indexPath] cell:selectedCell];
}
}

-(NSIndexPath *)indexPathWithoutSectionsFrom:(NSIndexPath *)indexPath
{
long sectionNum = indexPath.section;
long rowNum = sectionNum * self.elementsInRow + indexPath.row;
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:rowNum inSection:0];
return newIndexPath;
}

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return self.numOfSections;
}

-(void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
dispatch_async(dispatch_get_main_queue(), ^{
if (self.shouldReloadUIElements == NO)
{
return;
}

if (self.autoResize && self.selfHeightConstraint)
{
BOOL isTheFirstCellInTheLastSection = (indexPath.section == self.numOfSections - 1) && indexPath.row == 0;
if (isTheFirstCellInTheLastSection)
{
CGFloat newHeight = CGRectGetMaxY(cell.frame) + self.cellSpacing;

self.selfHeightConstraint.constant = newHeight;

if (self.bounds.size.height != newHeight)
{
CGRect frame = self.bounds;
frame.size.height = newHeight;
[self setBounds:frame];
}

[self.superview layoutIfNeeded];
[self layoutIfNeeded];
}
}
});
}

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
if (self.shouldReloadUIElements == NO)
{
return collectionView.contentInset;
}

NSInteger cellsCount = [collectionView numberOfItemsInSection:section];
CGFloat horizontalInset = (collectionView.bounds.size.width - (cellsCount * self.cellSize) - ((cellsCount - 1) * self.cellSpacing)) * 0.5;
horizontalInset = MAX(horizontalInset, 0.0);

BOOL isLastSection = (section == self.numOfSections - 1);

CGFloat verticalTopInset = self.cellSpacing;

CGFloat verticalBottomInset = verticalTopInset;

if (section == 0 && isLastSection == NO)
{
if (self.heightMiddleSpacing)
{
verticalBottomInset += self.heightMiddleSpacing;
}
verticalBottomInset /= 2;
}

if (section > 0)
{
if (self.heightMiddleSpacing)
{
verticalTopInset += self.heightMiddleSpacing;
}
verticalTopInset /= 2;

if (isLastSection == NO)
{
if (self.heightMiddleSpacing)
{
verticalBottomInset += self.heightMiddleSpacing;
}
verticalBottomInset /= 2;
}
}

return UIEdgeInsetsMake(verticalTopInset, horizontalInset, verticalBottomInset, horizontalInset);
}

@end

And to make it work, all we need to do in the parent view is:

self.collectionView.delegateCenteringCollection = self;
self.collectionView.dataSourceCount = 5; // Or whatever number we want!

In the storyboard: we need to create collectionView of this class and set the "elements in row" value, also set the "Cell Identifier" and the "Cell relative size" between 0 to 1 (the "Cell relative size" value: will calculate the cell size & paddings according to the collectionView width & height).
And at last - set "autoResize" to "true" if you want that the collection view will resize its own height constraint(if exist) automatically according to the number of rows. If we set "autoResize" to true, the height constraint that we set to the collectionView will determine the height of a single row. If our collectionView should grow for example to 3 rows, it will multiple our collectionview height constraint by 3.

And it works like a charm!

Cells order in UICollectionView

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 :
Sample Image

In UICollectionView, the order changes automatically

You should definitely not be using viewWithTag. The reason for this is because cells are dequeued, which means that cells that have been taken off screen by scrolling are reused for the cells which are about to come on screen. This is done to save memory. Sometimes the problem with the order can be because previously used cells are not updated quick enough when they are presented to the user. The solution to this if you are using a network request can be to firstly to streamline your request and then cache any heavy data that is returned, such as images.

CollectionView Cell order messes up on scroll

This is a common problem with new iOS developers and table views/collection views.

The rule: When you return a cell in your cellForItemAt/cellForRowAt method, you always need to fully configure the cell. Don't assume it comes to you as a blank page.

Your if statement says if indexPath.item % 2 != 0 (if the item count is even) then set the background color to gray.

What if the item value is odd? You leave it whatever color it was to start with. I'm guessing that your cells start out with a blue background color. But, what if this is a recycled cell that was gray last time it was used?

The way your code is written, that cell will fall through the if statement and its (gray) background color won't get changed..

You need an else clause:

        if indexPath.item % 2 != 0 {               
cell.bookView.backgroundColor = .darkGray
} else {
cell.bookView.backgroundColor = .blue // Or whatever the default color is
}

And you need to do that for every single property of your cell that might ever change. If every 100th cell gets a box around it, then you need to write code that removes the box if it's not one of those rare 1 out of 100 cells, or you will wind up picking up a recycled cell that has a box around it from the last time it was used.

UICollectionView.reloadData() changes cell order | iOS Swift

You are creating a new label every time. You only add it as a subview if the tag isn't 19 but you always allocate a new label.

This means that when you reuse a cell, you are updating a new label, not the one that has been added to the cell, so your changes aren't visible.

You can either move your tag check to the top of the function and use that to control the allocation of the label, or define matchCollectionLabel as an optional and then you can check it for nil:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let reuseIdentifier = "MatchCollectionViewCell"
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! MatchCollectionViewCell

if cell.matchCollectionLabel == nil {
cell.matchCollectionLabel = UILabel(frame: CGRectMake(0,0,collectionView.bounds.width/3 - 3.0,15))
cell.matchCollectionLabel!.textColor = UIColor.blackColor()
cell.systemLayoutSizeFittingSize(cell.frame.size, withHorizontalFittingPriority: UILayoutPriorityDefaultHigh, verticalFittingPriority: UILayoutPriorityDefaultLow)
cell.matchCollectionLabel!.font = UIFont(name: "Helvetica Neue", size:12)
cell.addSubview(cell.matchCollectionLabel!)
}

if indexPath.section == 0 {
switch indexPath.row {
case 0:
cell.matchCollectionLabel!.text = "Match #"
cell.backgroundColor = ImageEditor.colorWithHexString("EFEFF4")
cell.matchCollectionLabel!.textAlignment = NSTextAlignment.Right
break
case 1:
cell.matchCollectionLabel!.text = "Record"
cell.backgroundColor = ImageEditor.colorWithHexString("EFEFF4")
cell.matchCollectionLabel!.textAlignment = NSTextAlignment.Center
break
case 2:
cell.matchCollectionLabel!.text = "Outcome"
cell.backgroundColor = ImageEditor.colorWithHexString("EFEFF4")
break
default:
cell.matchCollectionLabel!.text = ""
cell.backgroundColor = ImageEditor.colorWithHexString("EFEFF4")
break
}
} else {
switch indexPath.row {
case 0:
cell.matchCollectionLabel!.text = "\(indexPath.section)"
if indexPath.section%2 == 0 {
cell.backgroundColor = ImageEditor.colorWithHexString("EFEFF4")
}
cell.matchCollectionLabel!.textAlignment = NSTextAlignment.Right
break
case 1:
cell.matchCollectionLabel!.text = matchesFromSelectedSession[indexPath.section - 1].matchRecord()
if indexPath.section%2 == 0 {
cell.backgroundColor = ImageEditor.colorWithHexString("EFEFF4")
}
cell.matchCollectionLabel!.textAlignment = NSTextAlignment.Center
break
case 2:
let outcome = matchesFromSelectedSession[indexPath.section - 1].matchOutcome()
switch outcome {
case "W":
cell.matchCollectionLabel!.text = "Win"
cell.matchCollectionLabel!.textColor = UIColor.greenColor()
break
case "D":
cell.matchCollectionLabel!.text = "Draw"
cell.matchCollectionLabel!.textColor = UIColor.blueColor()
break
case "L":
cell.matchCollectionLabel!.text = "Loss"
cell.matchCollectionLabel!.textColor = UIColor.redColor()
break
default:
cell.matchCollectionLabel!.textColor = UIColor.blackColor()
break
}
if indexPath.section%2 == 0 {
cell.backgroundColor = ImageEditor.colorWithHexString("EFEFF4")
}
break
default:
if indexPath.section%2 == 0 {
cell.backgroundColor = ImageEditor.colorWithHexString("EFEFF4")
}
cell.matchCollectionLabel!.text = ""
break
}
}
return cell
}


Related Topics



Leave a reply



Submit