Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keeping the contentOffset in a UICollectionView while rotating Interface Orientation

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} …

UICollectionView in portait

… should result to the content offset in landscape also with {bounds.size.width * 2, 0} (and vice versa).

UICollectionView in landscape

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.

like image 818
Tobias Kräntzer Avatar asked Nov 21 '12 09:11

Tobias Kräntzer


People also ask

What is UICollectionView flow layout?

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.

How does UICollectionView work?

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.


2 Answers

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

like image 104
Gurjinder Singh Avatar answered Oct 03 '22 12:10

Gurjinder Singh


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]; } 
like image 26
fz. Avatar answered Oct 03 '22 11:10

fz.