Search code examples
iosobjective-cuicollectionviewuicollectionviewcellcalayer

Strange UICollectionViewCell behaviour if float size


Updated: Sample project is github link

USE iPHONE 6 simulator

2 possible ways to achieve this bug. Just build and run and see. Or uncomment 256 row

//return CGSizeMake(widthAndHeight * self.venueLayoutZoom, widthAndHeight * self.venueLayoutZoom);

and press "Remove column" button

I have UICollectionView with a lot of UICollectionViewCells. My UI makes me to use zero space between cells. I have a pinch gesture in my UICollectionView. So sometimes sizeForItem:atIndexPath: returns me a big float numbers (like 11.821123411231). The problem is: If I don't round these floats I have a strange behaviour sometimes - enter image description here

It should be enter image description here

If I round up or down sizeForItem:atIndexPath: it looks great but there are spaces between cells enter image description here

I don't know what to do. It is really necessary to do without these spaces or strange cells. My flow layout is this

Controller Code:

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout *)collectionViewLayout
  sizeForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
    CGFloat widthAndHeight = [self widthAndHeightForCollectionView:collectionView];
    CGFloat result = widthAndHeight * self.venueLayoutZoom;
    return CGSizeMake(result, result);
}

- (void)didReceivePinchGesture:(UIPinchGestureRecognizer *)gesture
{
    static CGFloat scaleStart;

    if (gesture.state == UIGestureRecognizerStateBegan) {
        scaleStart = self.venueLayoutZoom;
    }
    else if (gesture.state == UIGestureRecognizerStateChanged) {

        self.venueLayoutZoom = scaleStart * gesture.scale;
    }
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    VenueLayoutCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kVenueLayoutCellReuseIdentifier forIndexPath:indexPath];

    self.activeCollectionViewCellsDictionary[indexPath] = cell;
    if (self.activeCollectionViewObjects.count > indexPath.section) {
        NSArray *rows = self.activeCollectionViewObjects[indexPath.section];
        if (rows.count > indexPath.row) {
            if ([rows[indexPath.row] isKindOfClass:[VenueLayoutCellObject class]]) {
                VenueLayoutCellObject *object = rows[indexPath.row];
                cell.cellObject = object;

            }
        }
    }
    return cell;
}

Cell Code:

@property (nonatomic, strong) CALayer *circularLayer;

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self allocAndAddLayers];
}

- (void)allocAndAddLayers
{
    self.circularLayer = [CALayer layer];
    [self.layer addSublayer:self.circularLayer];
}

#pragma mark - Property Set

- (void)setCellObject:(VenueLayoutCellObject *)cellObject
{
    self->_cellObject = cellObject;
    self.objectBackgroundColor = cellObject.objectColor;
    self.type = cellObject.type;
}

- (void)prepareForReuse
{
    [self.circularLayer removeFromSuperlayer];
    [self allocAndAddLayers];
}

- (void)setType:(VenueLayoutObjectType)type
{
    self->_type = type;
    [self updateInderfaceDependOnType];
}

- (void)layoutSubviews
{
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    [self updateRoundedCorners];
    [CATransaction commit];
    [super layoutSubviews];
}

- (void)updateRoundedCorners
{
    CGRect bounds = self.bounds;
    CGRect frame = self.frame;
    self.circularLayer.frame = CGRectMake(0, 0, frame.size.width, frame.size.height);
}

- (void)setLayerBackgroundColor:(UIColor *)color
{
    self.circularLayer.backgroundColor = color.CGColor;
    self.objectMaskLayer.strokeColor = color.CGColor;
    self.deselectedColor = color;
}

- (void)updateInderfaceDependOnType
{
    UIColor *tempObjectColor = [UIColor grayColor];
    UIColor *objectColor = self.objectBackgroundColor ? : tempObjectColor;
    [self setLayerBackgroundColor:objectColor];
}

Updated Code:

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout *)collectionViewLayout
  sizeForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
    CGFloat widthAndHeight = self.widthAndHeightForActiveCollectionView;
    CGFloat result = lroundf(widthAndHeight * self.venueLayoutZoom);
    CGFloat width = result;
    if (indexPath.row > self.increasedWidthInitialIndex) {
        width++;
    }
    return CGSizeMake(width, result);
}

CGFloat widthAndHeight = CGRectGetWidth(self.activeCollectionView.bounds) / maxCount;
    NSNumber *widthNumber = @(CGRectGetWidth(self.activeCollectionView.bounds));
    NSInteger count = widthNumber.integerValue % maxCount;
    maxCount--;
    self.increasedWidthInitialIndex = maxCount - count;

Updated: If I use low item size (e.g CGSizeMake(7.f, 7.f)) cells doesn't fit whole collection view but space still exists


Solution

  • Eventually setting width / height to half pixels changes the scale according to the device pixel depth (retina vs non-retina) - more info here

    When you try and divide your screen to non-whole numbers, i.e. for screen width of 320 create 6 cells of 53.3 with might cause the UI to break.

    You should try and find the remainder of your devision, i.e:

    320 % 6 -> 2
    

    And then make 2 cells 1 pixel wider. So instead of 6 cells consisting of 53.3 pixels wide, you will have 2 consisting of 54 and 4 more of 53, then everything will fit correctly.

    EDIT:

    After going over your project, I have added printouts of your collection see what issues could be caused by widths, Iv'e added this method in the ViewController.m:

    - (void)printoutGrid
    {
        NSLog(@"collectionview bounds: %f, %@", self.activeCollectionView.bounds.size.width, NSStringFromCGSize(self.activeCollectionView.contentSize));
        CGFloat calculatedWidth = [self widthAndHeightForActiveCollectionViewItem];
        CGFloat result = floor(calculatedWidth * self.venueLayoutZoom);
    
        for (int rowAt = 0; rowAt < self.activeCollectionViewObjects.count; rowAt++)
        {
            NSArray* arrayAt = self.activeCollectionViewObjects[rowAt];
            NSString* rowString = @"|";
    
            for (int colAt = 0; colAt < arrayAt.count; colAt++)
            {
                if (colAt > self.increasedWidthInitialIndex)
                {
                    rowString = [rowString stringByAppendingString:[NSString stringWithFormat:@"%d|", (int)result + 1]];
                }
                else
                {
                    rowString = [rowString stringByAppendingString:[NSString stringWithFormat:@"%d|", (int)result]];
                }
            }
    
            NSLog(@"%@", rowString);
        }
    }
    

    And called it after every time you reload the collection or invalidate the layout, Iv'e found only that you should use floor instead of lroundf.

    This method will help you debug your widths for the future.

    But, after going over your project settings, something seemed off in your simulator, I noticed it was too large / out of scale and not retina, I went to your project settings and saw that your project is not retina compatible - which means the UI will not use Auto-Scaling -> which means it will use the same resolution (320 x 518) for all devices and what it does, it stretches the UI - meaning in iPhone 6 it multiplies it, and the stretching is being anti-analysed, which causes these so-called empty spaces between cells even though there is not space there.

    What you need to do, is go to your project settings -> and in general scroll down to "Launch Screen File" and select "LaunchScreen" from the list:

    enter image description here

    This seems to solve the issue for this project. Let me know if you have anymore question and if it works.

    Please note - it will require you to change size calculation a-bit because the resolution changes again after "viewDidLayoutSubviews" and not only when the view loads.

    Good luck!