Dynamically setting layout on UICollectionView causes inexplicable contentOffset change

UICollectionViewLayout contains the overridable method targetContentOffsetForProposedContentOffset: which allows you to provide the proper content offset during a change of layout, and this will animate correctly. This is available in iOS 7.0 and above


I have been pulling my hair out over this for days and have found a solution for my situation that may help. In my case I have a collapsing photo layout like in the photos app on the ipad. It shows albums with the photos on top of each other and when you tap an album it expands the photos. So what I have is two separate UICollectionViewLayouts and am toggling between them with [self.collectionView setCollectionViewLayout:myLayout animated:YES] I was having your exact problem with the cells jumping before animation and realized it was the contentOffset. I tried everything with the contentOffset but it still jumped during animation. tyler's solution above worked but it was still messing with the animation.

Then I noticed that it happens only when there were a few albums on the screen, not enough to fill the screen. My layout overrides -(CGSize)collectionViewContentSize as recommended. When there are only a few albums the collection view content size is less than the views content size. That's causing the jump when I toggle between the collection layouts.

So I set a property on my layouts called minHeight and set it to the collection views parent's height. Then I check the height before I return in -(CGSize)collectionViewContentSize I ensure the height is >= the minimum height.

Not a true solution but it's working fine now. I would try setting the contentSize of your collection view to be at least the length of it's containing view.

edit: Manicaesar added an easy workaround if you inherit from UICollectionViewFlowLayout:

-(CGSize)collectionViewContentSize { //Workaround
    CGSize superSize = [super collectionViewContentSize];
    CGRect frame = self.collectionView.frame;
    return CGSizeMake(fmaxf(superSize.width, CGRectGetWidth(frame)), fmaxf(superSize.height, CGRectGetHeight(frame)));
}

This issue bit me as well and it seems to be a bug in the transition code. From what I can tell it tries to focus on the cell that was closest to the center of the pre-transition view layout. However, if there doesn't happen to be a cell at the center of the view pre-transition then it still tries to center where the cell would be post-transition. This is very clear if you set alwaysBounceVertical/Horizontal to YES, load the view with a single cell and then perform a layout transition.

I was able to get around this by explicitly telling the collection to focus on a specific cell (the first cell visible cell, in this example) after triggering the layout update.

[self.collectionView setCollectionViewLayout:[self generateNextLayout] animated:YES];

// scroll to the first visible cell
if ( 0 < self.collectionView.indexPathsForVisibleItems.count ) {
    NSIndexPath *firstVisibleIdx = [[self.collectionView indexPathsForVisibleItems] objectAtIndex:0];
    [self.collectionView scrollToItemAtIndexPath:firstVisibleIdx atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
}