I'm trying to handle interface orientation changes in a UICollectionViewController. What I'm trying to achieve is, that I want to have the same contentOffset after an interface rotation. Meaning, that it should be changed corresponding to the ratio of the bounds change.
Starting in portrait with a content offset of {bounds.size.width * 2, 0} …
… should result to the content offset in landscape also with {bounds.size.width * 2, 0} (and vice versa).
Calculating the new offset is not the problem, but don't know, where (or when) to set it, to get a smooth animation. What I'm doing so fare is invalidating the layout in willRotateToInterfaceOrientation:duration:
and resetting the content offset in didRotateFromInterfaceOrientation:
:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration; { self.scrollPositionBeforeRotation = CGPointMake(self.collectionView.contentOffset.x / self.collectionView.contentSize.width, self.collectionView.contentOffset.y / self.collectionView.contentSize.height); [self.collectionView.collectionViewLayout invalidateLayout]; } - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation; { CGPoint newContentOffset = CGPointMake(self.scrollPositionBeforeRotation.x * self.collectionView.contentSize.width, self.scrollPositionBeforeRotation.y * self.collectionView.contentSize.height); [self.collectionView newContentOffset animated:YES]; }
This changes the content offset after the rotation.
How can I set it during the rotation? I tried to set the new content offset in willAnimateRotationToInterfaceOrientation:duration:
but this results into a very strange behavior.
An example can be found in my Project on GitHub.
Overview. A flow layout is a type of collection view layout. Items in the collection view flow from one row or column (depending on the scrolling direction) to the next, with each row containing as many cells as will fit. Cells can be the same sizes or different sizes.
The collection view presents items onscreen using a cell, which is an instance of the UICollectionViewCell class that your data source configures and provides. In addition to its cells, a collection view can present data using other types of views.
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
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:
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]; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With