UICollectionView: Animate cell size change on selection

IosSwiftUicollectionview

Ios Problem Overview


What I want to do is change the size of an UICollectionViewCell, and to animate that change, when the cell is selected. I already managed to do that unanimated by marking a cell as selected in collectionView: didSelectItemAtIndexPath: and then calling reloadData on my UICollectionView, displaying the selected cell with a different size.

Nevertheless, this happens all at once, and I have no clue how to get that change in size animated. Any ideas?

I already found https://stackoverflow.com/questions/12885003/animate-uicollectionview-cells-on-selection, but the answer was to unspecific for me and I didn't figure out yet if it could also help me in my case.

Ios Solutions


Solution 1 - Ios

With the help of Chase Roberts, I now found the solution to my problem. My code basically looks like this:

// Prepare for animation

[self.collectionView.collectionViewLayout invalidateLayout];
UICollectionViewCell *__weak cell = [self.collectionView cellForItemAtIndexPath:indexPath]; // Avoid retain cycles
void (^animateChangeWidth)() = ^()
{
    CGRect frame = cell.frame;
    frame.size = cell.intrinsicContentSize;
    cell.frame = frame;
};

// Animate

[UIView transitionWithView:cellToChangeSize duration:0.1f options: UIViewAnimationOptionCurveLinear animations:animateChangeWidth completion:nil];

For further explanation:

  1. One of the most important step is the invalidateLayout call on the UICollectionView.collectionViewLayout you are using. If you forget it, it is likely to happen that cells will grow over the borders of your collection - that's something you most probably don't want to happen. Also consider that your code should at some point present the new size of your cell to your layout. In my case (I'm using an UICollectionViewFlowLayout), I adjusted the collectionView:layout:sizeForItemAtIndexPath method to reflect the new size of the modified cell. Forgetting that part, nothing will happen to your cell sizes at all if you first call invalidateLayout and then try to animate the change in size.

  2. Most strange (at least to me) but important is that you call invalidateLayout not inside the animation block. If you do so, the animation won't display smooth but glitchy and flickering.

  3. You have to call UIViews transitionWithView:duration:options:animations:completion: method with the cell as argument for transitionWithView, not the whole collection view, because it's the cell you want to get animated, not the entire collection.

  4. I used the intrinsicContentSize method of my cell to return the size I want - in my case, I grow and shrink the width of the cell to either display a button or hide it, depending on the selection state of the cell.

I hope this will help some people. For me it took several hours to figure out how to make that animation work correctly, so I try to free others from that time-consuming burden.

Solution 2 - Ios

Animating the size of a cell works the same way for collection views as it does for table views. If you want to animate the size of a cell, have a data model that reflects the state of the cell (in my case, I just use a flag (CollectionViewCellExpanded, CollectionViewCellCollapsed) and return a CGSize accordingly.

When you call and empty performBathUpdates call, the view animates automatically.

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    [collectionView performBatchUpdates:^{
        // append your data model to return a larger size for
        // cell at this index path
    } completion:^(BOOL finished) {
    
    }];
}

Solution 3 - Ios

I've had success animating changes by using -setCollectionViewLayout:animated: to create a new instance of the same collection view layout.

For example, if you're using a UICollectionViewFlowLayout, then:

// Make sure that your datasource/delegate are up-to-date first

// Then animate the layout changes
[collectionView setCollectionViewLayout:[[UICollectionViewFlowLayout alloc] init] animated:YES];

Solution 4 - Ios

[UIView transitionWithView:collectionView 
                  duration:.5 
                   options:UIViewAnimationOptionTransitionCurlUp 
                animations:^{

    //any animatable attribute here.
    cell.frame = CGRectMake(3, 14, 100, 100);

} completion:^(BOOL finished) {

    //whatever you want to do upon completion

}];

Play around with something along those lines inside your didselectItemAtIndexPath method.

Solution 5 - Ios

This is the simplest solution I have found - works like a charm. Smooth animation, and does not mess up the layout.

Swift

    collectionView.performBatchUpdates({}){}

Obj-C

    [collectionView performBatchUpdates:^{ } completion:^(BOOL finished) { }];

The blocks (closures in Swift) should be intentionally left empty!

Solution 6 - Ios

A good way to animate the cell size is to override isHighlighted in the collectionViewCell itself.

class CollectionViewCell: UICollectionViewCell {

    override var isHighlighted: Bool {
        didSet {
            if isHighlighted {
                UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: {
                    self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
                }, completion: nil)
            } else {
                UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: {
                    self.transform = CGAffineTransform(scaleX: 1, y: 1)
                }, completion: nil)
            }
        }
    }

}

Solution 7 - Ios

Using performBatchUpdates will not animate the layout of the cell contents. Instead, you can use the following:

    collectionView.collectionViewLayout.invalidateLayout()
    
    UIView.animate(
        withDuration: 0.4,
        delay: 0.0,
        usingSpringWithDamping: 1.0,
        initialSpringVelocity: 0.0,
        options: UIViewAnimationOptions(),
        animations: {
            self.collectionView.layoutIfNeeded()
        },
        completion: nil
    )

This gives a very smooth animation.

This is similar to Ignatius Tremor's answer, but the transition animation did not work properly for me.

See https://github.com/cnoon/CollectionViewAnimations for a complete solution.

Solution 8 - Ios

This worked for me.

	collectionView.performBatchUpdates({ () -> Void in
		let ctx = UICollectionViewFlowLayoutInvalidationContext()
		ctx.invalidateFlowLayoutDelegateMetrics = true
		self.collectionView!.collectionViewLayout.invalidateLayoutWithContext(ctx)
	}) { (_: Bool) -> Void in
	}

Solution 9 - Ios

class CollectionViewCell: UICollectionViewCell {
   override var isHighlighted: Bool {
       if isHighlighted {
            UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut, animations: {
                self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
            }) { (bool) in
                UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut, animations: {
                    self.transform = CGAffineTransform(scaleX: 1, y: 1)
                }, completion: nil)
            }
        }
   }
}

One just needs to check if the isHighlighted variable was changed to true, then use UIView animation and animate back in the completion handler.

Solution 10 - Ios

Believe it or not this works

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPat`h: IndexPath) {
            
            // Make your item selected in your data source here
                    
             UIView.animate(withDuration: 0.5) {
            // call reload cells in the animation block and it'll update the selected cell's height with an animation 
                        collectionView.reloadItems(at: [indexPath])
                    }
    }

By the way I'm using automatic cell heights i.e. (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJan Z.View Question on Stackoverflow
Solution 1 - IosJan Z.View Answer on Stackoverflow
Solution 2 - IosAndy PoesView Answer on Stackoverflow
Solution 3 - IoscodepersonView Answer on Stackoverflow
Solution 4 - IosChase RobertsView Answer on Stackoverflow
Solution 5 - IosPavel GurovView Answer on Stackoverflow
Solution 6 - IosCristian PenaView Answer on Stackoverflow
Solution 7 - IosphatmannView Answer on Stackoverflow
Solution 8 - IoseonilView Answer on Stackoverflow
Solution 9 - IosAlexanderView Answer on Stackoverflow
Solution 10 - IosMark BridgesView Answer on Stackoverflow