Keeping the contentOffset in a UICollectionView while rotating Interface Orientation Keeping the contentOffset in a UICollectionView while rotating Interface Orientation objective-c objective-c

Keeping the contentOffset in a UICollectionView while rotating Interface Orientation


You can either do this in the view controller:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {    super.viewWillTransition(to: size, with: coordinator)    guard let collectionView = collectionView else { return }    let offset = collectionView.contentOffset    let width = collectionView.bounds.size.width    let index = round(offset.x / width)    let newOffset = CGPoint(x: index * size.width, y: offset.y)    coordinator.animate(alongsideTransition: { (context) in        collectionView.reloadData()        collectionView.setContentOffset(newOffset, animated: false)    }, completion: nil)}

Or in the layout itself: https://stackoverflow.com/a/54868999/308315


Solution 1, "just snap"

If what you need is only to ensure that the contentOffset ends in a right position, you can create a subclass of UICollectionViewLayout and implement targetContentOffsetForProposedContentOffset: method. For example you could do something like this to calculate the page:

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset{    NSInteger page = ceil(proposedContentOffset.x / [self.collectionView frame].size.width);    return CGPointMake(page * [self.collectionView frame].size.width, 0);}

But the problem that you'll face is that the animation for that transition is extremely weird. What I'm doing on my case (which is almost the same as yours) is:

Solution 2, "smooth animation"

1) First I set the cell size, which can be managed by collectionView:layout:sizeForItemAtIndexPath: delegate method as follows:

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

Note that [self.view bounds] will change according to the device rotation.

2) When the device is about to rotate, I'm adding an imageView on top of the collection view with all resizing masks. This view will actually hide the collectionView weirdness (because it is on top of it) and since the willRotatoToInterfaceOrientation: method is called inside an animation block it will rotate accordingly. I'm also keeping the next contentOffset according to the shown indexPath so I can fix the contentOffset once the rotation is done:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation                                duration:(NSTimeInterval)duration{    // Gets the first (and only) visible cell.    NSIndexPath *indexPath = [[self.collectionView indexPathsForVisibleItems] firstObject];    KSPhotoViewCell *cell = (id)[self.collectionView cellForItemAtIndexPath:indexPath];    // Creates a temporary imageView that will occupy the full screen and rotate.    UIImageView *imageView = [[UIImageView alloc] initWithImage:[[cell imageView] image]];    [imageView setFrame:[self.view bounds]];    [imageView setTag:kTemporaryImageTag];    [imageView setBackgroundColor:[UIColor blackColor]];    [imageView setContentMode:[[cell imageView] contentMode]];    [imageView setAutoresizingMask:0xff];    [self.view insertSubview:imageView aboveSubview:self.collectionView];    // Invalidate layout and calculate (next) contentOffset.    contentOffsetAfterRotation = CGPointMake(indexPath.item * [self.view bounds].size.height, 0);    [[self.collectionView collectionViewLayout] invalidateLayout];}

Note that my subclass of UICollectionViewCell has a public imageView property.

3) Finally, the last step is to "snap" the content offset to a valid page and remove the temporary imageview.

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{    [self.collectionView setContentOffset:contentOffsetAfterRotation];    [[self.view viewWithTag:kTemporaryImageTag] removeFromSuperview];}


The "just snap" answer above didn't work for me as it frequently didn't end on the item that was in view before the rotate. So I derived a flow layout that uses a focus item (if set) for calculating the content offset. I set the item in willAnimateRotationToInterfaceOrientation and clear it in didRotateFromInterfaceOrientation. The inset adjustment seems to be need on IOS7 because the Collection view can layout under the top bar.

@interface HintedFlowLayout : UICollectionViewFlowLayout@property (strong)NSIndexPath* pathForFocusItem;@end@implementation HintedFlowLayout-(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset{    if (self.pathForFocusItem) {        UICollectionViewLayoutAttributes* layoutAttrs = [self layoutAttributesForItemAtIndexPath:self.pathForFocusItem];        return CGPointMake(layoutAttrs.frame.origin.x - self.collectionView.contentInset.left, layoutAttrs.frame.origin.y-self.collectionView.contentInset.top);    }else{        return [super targetContentOffsetForProposedContentOffset:proposedContentOffset];    }}@end