8

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

Brett
  • 1,647
  • 16
  • 34
  • I'm going to experiment with this in the next few days. I believe that if you have the image view layout without height or width constraints and constrain leading, trailing, top and bottom space to the container view, then you should get the desired effect. – johnrechd Dec 06 '13 at 22:41
  • Hi I am having the exact same problem. Could you let me know if you have found a solution please ! – Marty W Dec 30 '13 at 22:12

3 Answers3

40

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).

Frank Schmitt
  • 25,648
  • 10
  • 58
  • 70
  • 7
    I would like to press 3 times on Up Arrow, unfortunately only ones permitted. – Dimentar May 29 '14 at 14:14
  • Told all my team members to come thumb this up FFS! – Shizam May 29 '15 at 18:10
  • 5
    Rad. For what it's worth, this also works for me inside an animated layout-to-layout transition if I only call `layoutIfNeeded` (i.e. not wrapped in an `animateWithDuration` block. – par Oct 28 '15 at 22:31
4

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

Brett
  • 1,647
  • 16
  • 34
1

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

Community
  • 1
  • 1
Marty W
  • 415
  • 1
  • 5
  • 12