Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animating UICollectionView cell resize triggered by switching layouts

I am trying to animate between various UICollectionViewFlowLayout subclasses. Each flow layout specifies a different item size. One displays a grid with 3 items across, another a grid with 2 items across, and the third with only 1 item across (Looks like a UITableView).

I am calling setCollectionViewLayout:animated: to do this. The animation of the cell sizes works perfectly. However the subviews are a bit of a mess. Each cell contains an image and a label (Have removed the label for now). I want the image to animate its change in size as the cell changes in size, likewise the label.

If I use auto layout (not very good at this yet) to layout the subviews of my cell then at the start of the animation the image is resized to the destination cell frame. I understand how UIView animations work in that they change the animated property straight away and the animation is done in the presentation layer, but I expected auto layout to work differently somehow.

If I remove auto layout and do the following in initWithFrame:

[[self imageView] setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin];

then the size of the image subview animates quite nicely, however after cycling through a few animations the image subview size and location is incorrect in some cases.

I've tried laying out the subviews in layoutSubviews, but this gives the same result as AutoLayout.

All of the demos I've seen on github for animating between UICollectionViewLayouts have just a coloured background or a subview which fills the entire cell. Has anyone managed to get this working?

Below are images of what is occurring (red line is border of UIImageView):

First Layout

Second Layout

Third Layout

Back to first layout, but subview frames are wrong

like image 664
Brett Avatar asked Nov 20 '13 04:11

Brett


3 Answers

To get this working with AutoLayout, you need to put the following in your UICollectionViewCell subclass:

- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    [UIView animateWithDuration:animationDuration animations:^{
        [self layoutIfNeeded];
    }];
}

The default animation duration is around 0.33 seconds (as determined experimentally).

like image 153
Frank Schmitt Avatar answered Jan 05 '23 01:01

Frank Schmitt


So the solution I went with was to turn off Auto Layout (as pointed out by Marty), but I also removed all Auto resizing masks too. I created three different hardcoded layouts in the UICollectionViewCell subclass for each UICollectionViewFlowLayout they were going to be displayed in.

GSProductCollectionViewCell.m

-(void)setLayoutForWideLayoutWithDestinationSize:(CGSize)size
{
    [[self imageView] setFrame:CGRectMake( 5.0f, 5.0f, (int)(size.width * 0.3f), size.height - 10.0f)];

    [[self titleLabel] setFrame:CGRectMake( self.imageView.right + 5.0f, 5.0f, size.width - self.imageView.right - 15.0f, 25.0f)];
    [[self titleLabel] setFont:[UIFont fontWithName:self.titleLabel.font.fontName size:12.0f]];

    [[self titleBackgroundView] setFrame:self.titleLabel.frame];
    [[self titleBackgroundView] setAlpha:1.0f];

    [[self descriptionLabel] setAlpha:1.0f];
    [[self descriptionLabel] setFrame:CGRectMake( self.titleLabel.left,     self.titleLabel.bottom + 5.0f, self.titleLabel.width, 25.0f)];

     [...]
}

-(void)setLayoutForSqaureLayoutWithDestinationSize:(CGSize)size
{
    [[self imageView] setFrame:CGRectMake( 5.0f, 5.0f, size.width - 10.0f, size.height - 10.0f - 30.0f)];

    [[self titleLabel] setFrame:CGRectMake( 5.0f, size.height - 30.0f, size.width - 10.0f, 25.0f)];
    [[self titleLabel] setFont:[UIFont fontWithName:self.titleLabel.font.fontName size:10.0f]];

    [[self titleBackgroundView] setFrame:self.titleLabel.frame];
    [[self titleBackgroundView] setAlpha:0.0f];

    [[self descriptionLabel] setAlpha:0.0f];
    [[self descriptionLabel] centerView];

    [...]
}

-(void)setLayoutWithFrameSize:(CGSize)size forStyle:(GSProductLayoutStyle)layoutStyle
{
    switch (layoutStyle)
    {
        case kGSProductLayoutStyleGrid3:
        case kGSProductLayoutStyleGrid2:
            [self setLayoutForSqaureLayoutWithDestinationSize:size];
            break;

        case kGSProductLayoutStyleList:
            [self setLayoutForWideLayoutWithDestinationSize:size];
            break;

        default:
            break;
    }
}

Then when the user selects the cycle layout button I advance to the next layout and call do the following in my ViewController (Has a UICollectionView):

-(void)setLayoutStyle:(GSProductLayoutStyle)layoutStyle
{
    if (_layoutStyle != layoutStyle)
    {
        _layoutStyle = layoutStyle;

        UICollectionViewFlowLayout *layout;

        switch (_layoutStyle)
        {
           case kGSProductLayoutStyleGrid3:
                layout = [GSProductCollectionViewGridLayout new];
                break;

            case kGSProductLayoutStyleGrid2:
                layout = [GSProductCollectionViewGridTwoLayout new];
                break;

            case kGSProductLayoutStyleList:
                layout = [GSProductCollectionViewListLayout new];
                break;

            default:
                layout = [GSProductCollectionViewGridLayout new];
                break;
        }

        CGSize itemSize = layout.itemSize;

        [self setCollectionViewLayout:layout animated:YES];

        [[self visibleCells] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

            [(GSProductCollectionViewCell*)obj setLayoutStyle:_layoutStyle];

            [UIView animateWithDuration:0.3f animations:^{

                [(GSProductCollectionViewCell*)obj setLayoutWithFrameSize:itemSize forStyle:self.layoutStyle];

            }];
        }];

    }
}

This gives me complete control over each layout in the Cell for each type of FlowLayout subclass. The animations are smooth and I can also fade out any views which I don't want in a particular layout.

The result of this is a UICollectionView which behaves a lot like the iPhone Ebay Apps grid view behaviour, but better in my opinion.

Sorry I can't paste any more code or put it on GitHub as it is from Client Code. I am happy to answer any questions though.

Cheers, Brett

like image 33
Brett Avatar answered Jan 05 '23 00:01

Brett


Hi Brett after looking everywhere for an answer I found this which helped me:

UICollectionView cell subviews do not resize

I ended up turning auto layout off and just using the legacy auto resize features of IB.

Works a treat! Hope it helps you!

Edit: sorry I just realised you did this programmatically, could you call reload data in set layouts animated on completion block to redraw your cells? As per this:

UICollectionView cells, auto layout and collectionview layout changes

like image 44
Marty W Avatar answered Jan 05 '23 00:01

Marty W