According to Apple's documentation (and touted at WWDC 2012), it is possible to set the layout on UICollectionView
dynamically and even animate the changes:
You normally specify a layout object when creating a collection view but you can also change the layout of a collection view dynamically. The layout object is stored in the collectionViewLayout property. Setting this property directly updates the layout immediately, without animating the changes. If you want to animate the changes, you must call the setCollectionViewLayout:animated: method instead.
However, in practice, I've found that UICollectionView
makes inexplicable and even invalid changes to the contentOffset
, causing cells to move incorrectly, making the feature virtually unusable. To illustrate the problem, I put together the following sample code that can be attached to a default collection view controller dropped into a storyboard:
#import <UIKit/UIKit.h>
@interface MyCollectionViewController : UICollectionViewController
@end
@implementation MyCollectionViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"CELL"];
self.collectionView.collectionViewLayout = [[UICollectionViewFlowLayout alloc] init];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"CELL" forIndexPath:indexPath];
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"contentOffset=(%f, %f)", self.collectionView.contentOffset.x, self.collectionView.contentOffset.y);
[self.collectionView setCollectionViewLayout:[[UICollectionViewFlowLayout alloc] init] animated:YES];
NSLog(@"contentOffset=(%f, %f)", self.collectionView.contentOffset.x, self.collectionView.contentOffset.y);
}
@end
The controller sets a default UICollectionViewFlowLayout
in viewDidLoad
and displays a single cell on-screen. When the cells is selected, the controller creates another default UICollectionViewFlowLayout
and sets it on the collection view with the animated:YES
flag. The expected behavior is that the cell does not move. The actual behavior, however, is that the cell scroll off-screen, at which point it is not even possible to scroll the cell back on-screen.
Looking at the console log reveals that the contentOffset has inexplicably changed (in my project, from (0, 0) to (0, 205)). I posted a solution for the solution for the non-animated case (i.e. animated:NO
), but since I need animation, I'm very interested to know if anyone has a solution or workaround for the animated case.
As a side-note, I've tested custom layouts and get the same behavior.
swift 3.
UICollectionViewLayout subclass. The way cdemiris99 suggested:
I tested it myself using my own custom layout
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 thecontentOffset
. I tried everything with thecontentOffset
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:
Easy.
Animate your new layout and collectionView's contentOffset in the same animation block.
It will keep
self.collectionView.contentOffset
constant.UICollectionViewLayout
contains the overridable methodtargetContentOffsetForProposedContentOffset:
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