How to rotate a UICollectionView similar to the photos app and keep the current view centered?

10,812

Solution 1

I struggled with this for quite a while, until I at least found this 'cosmetic workaround':

Add a full screen UIImageView with the current image (and correct auto layout constraints set) on top of the collectionView during rotation. Like so:

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration: (NSTimeInterval)duration 
{

[self.collectionView.collectionViewLayout invalidateLayout];

    // show a UIImageView with the current image on top of the collectionView 
    // to cover the ugly animation
    self.imageViewOnTopOfCollectionView.image = [self imageForItemAtIndexPath:self.currentIndexPath];
    self.imageViewOnTopOfCollectionView.hidden = NO;

    // show a centered, very large 'fakeBackground' view on top of
    // the UICollectionView, but below the UIImageView
    self.fakeBackground.hidden = NO;
}

-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    // ... set correct contentOffset

    // hide the fake views again
    self.imageViewOnTopOfCollectionView.hidden = YES;
    self.fakeBackground.hidden = YES;
}

A large 'fakeBackground' would be an extra improvement to prevent parts of the ugly collection view animation being visible outside this imageViews frame while it rotates. E.g. an oversized (larger than the view's bounds in all dimensions) UIView with the same background color as the collectionView, with a z-Index just between the collectionView and the imageView.

Solution 2

This work like a charm:

-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout     *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return self.view.bounds.size;
}

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {

    int currentPage = collectionMedia.contentOffset.x / collectionMedia.bounds.size.width;
    float width = collectionMedia.bounds.size.height;

    [UIView animateWithDuration:duration animations:^{
        [self.collectionMedia setContentOffset:CGPointMake(width * currentPage, 0.0) animated:NO];
        [[self.collectionMedia collectionViewLayout] invalidateLayout];
    }];
}

Solution 3

Adding to @Goodsquirrel's great answer, here is how I've implemented it using current iOS8 API's and Swift:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator);

    // show the dummy imageView
    self.imageViewOnTopOfCollectionView.image = self.imageForItemAtIndex(self.currentIndex)
    self.imageViewOnTopOfCollectionView.hidden = false;

    coordinator.animateAlongsideTransition({ (context) -> Void in
        // update the dummy imageView's frame
        var frame:CGRect = self.imageViewOnTopOfCollectionView.frame;
        frame.size = size;
        self.imageViewOnTopOfCollectionView.frame = frame;

        // update the flow's item size
        if let flow = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            flow.itemSize = size;
        }

        // scroll to the current index
        self.scrollToItem(self.currentIndex);
        }, completion: { (context) -> Void in
            // remove the dummy imageView
            self.imageViewOnTopOfCollectionView.hidden = true;
    });
}
Share:
10,812

Related videos on Youtube

Shizam
Author by

Shizam

Been working in iOS since the day the private beta was launched, I've also spent time on Flash, Java (GUI) and C#, before that I did PHP & PERL. I am currently a tech lead on Dropbox mobile and before that was the mobile principal/lead at SmugMug.

Updated on August 15, 2022

Comments

  • Shizam
    Shizam over 1 year

    I have a photo gallery view that uses a UICollectionView with a UICollectionViewFlowLayout, it has pagingEnabled and scrolls horizontally showing only one view at a time.

    Works great till I try to rotate it...

    When I rotate the device, in willRotateToInterfaceOrientation:duration: I update the collectionView.contentOffset so it stays on the correct item and I resize the currentCell so it animates into the new dimensions. The problem is in the animation between the two states, the 'previous' orientation animates from the upper left corner AND flings into the view other cells. What is it I'm doing wrong such that the view being animated off the screen is FUBAR?

    Here is what it looks like in action:

    http://www.smugmug.com/gallery/n-3F9kD/i-BwzRzRf/A (ignore the choppy video, thats Quicktime's fault :p)

    Here is my willAnimateRotationToInterfaceOrientation:duration:

    - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
    
    // Update the flowLayout's size to the new orientation's size
    UICollectionViewFlowLayout *flow = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
    if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
        flow.itemSize = CGSizeMake(self.collectionView.frame.size.width, self.collectionView.frame.size.height);
    } else {
        flow.itemSize = CGSizeMake(self.collectionView.frame.size.width, self.collectionView.frame.size.height);
    }
    self.collectionView.collectionViewLayout = flow;
    [self.collectionView.collectionViewLayout invalidateLayout];
    
    // Get the currently visible cell
    PreviewCellView *currentCell = (PreviewCellView*)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:_currentIndex inSection:0]];
    
    // Resize the currently index to the new flow's itemSize
    CGRect frame = currentCell.frame;
    frame.size = flow.itemSize;
    currentCell.frame = frame;
    
    // Keep the collection view centered by updating the content offset
    CGPoint newContentOffset = CGPointMake(_currentIndex * frame.size.width, 0);
    self.collectionView.contentOffset = newContentOffset;
    }
    

    As far as I'm aware I can't find any sample code anywhere that illustrates how to make a 'photo gallery' style collection view that rotates gracefully.

  • Shizam
    Shizam over 10 years
    I also settled on this solution but have been holding out hope that there is an elegant way to do it just using the UICollectionView itself, its not looking like thats going to happen though :)
  • groomsy
    groomsy over 10 years
    The only thing I would add to this: In the willRotate method, calculate your current scroll position. then use that position in the didRotate. It took me a few minutes to figure that part out after looking at your code, but it dawned on me. Otherwise, elegant solution to an inelegant problem.
  • Ben Williams
    Ben Williams about 9 years
    An alterative to the fakeBackground is to simply set the backgroundColor of the imageViewOnTopOfCollectionView to be black (or some other solid color).
  • Goodsquirrel
    Goodsquirrel almost 9 years
    @alku83: I don't think this would solve the problem. The imageViewOnTopOfCollectionView is simply not big enough to cover up the views below completely during rotation. Changing it's background color does not make the view bigger. (The fakeBackground is not only necessary for images that are smaller than the screen, but also for full screen images.)
  • LShi
    LShi about 6 years
    Does this solve the fling-into problem that op mentioned?