How do you set the duration for UICollectionView Animations? - ios

I have a custom flow layout which is adjusting the attributes for cells when they are being inserted and deleted from the CollectionView with the following two functions, but I'm unable to figure out how you would adjust the default animation duration.
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
// Assign the new layout attributes
attributes.transform3D = CATransform3DMakeScale(0.5, 0.5, 0.5);
attributes.alpha = 0;
return attributes;
}
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
// Assign the new layout attributes
attributes.transform3D = CATransform3DMakeScale(0.5, 0.5, 0.5);
attributes.alpha = 0;
return attributes;
}

To solve problem without hack that was proposed in the answer by gavrix
you could subclass UICollectionViewLayoutAttributes with new property CABasicAnimation *transformAnimation, than create custom transformation with a suitable duration and assign it to attributes in initialLayoutAttributesForAppearingItemAtIndexPath, then in UICollectionViewCell apply the attributes as needed:
#interface AnimationCollectionViewLayoutAttributes : UICollectionViewLayoutAttributes
#property (nonatomic, strong) CABasicAnimation *transformAnimation;
#end
#implementation AnimationCollectionViewLayoutAttributes
- (id)copyWithZone:(NSZone *)zone
{
AnimationCollectionViewLayoutAttributes *attributes = [super copyWithZone:zone];
attributes.transformAnimation = _transformAnimation;
return attributes;
}
- (BOOL)isEqual:(id)other {
if (other == self) {
return YES;
}
if (!other || ![[other class] isEqual:[self class]]) {
return NO;
}
if ([(( AnimationCollectionViewLayoutAttributes *) other) transformAnimation] != [self transformAnimation]) {
return NO;
}
return YES;
}
#end
In Layout class
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
AnimationCollectionViewLayoutAttributes* attributes = (AnimationCollectionViewLayoutAttributes* )[super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
transformAnimation.duration = 1.0f;
CGFloat height = [self collectionViewContentSize].height;
transformAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, 2*height, height)];
transformAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, attributes.bounds.origin.y, 0)];
transformAnimation.removedOnCompletion = NO;
transformAnimation.fillMode = kCAFillModeForwards;
attributes.transformAnimation = transformAnimation;
return attributes;
}
+ (Class)layoutAttributesClass {
return [AnimationCollectionViewLayoutAttributes class];
}
then in UICollectionViewCell apply the attributes
- (void) applyLayoutAttributes:(AnimationCollectionViewLayoutAttributes *)layoutAttributes
{
[[self layer] addAnimation:layoutAttributes.transformAnimation forKey:#"transform"];
}

change CALayer's speed
#implementation Cell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.layer.speed =0.2;//default speed is 1
}
return self;
}

Building on #rotava's answer, you can temporarily set the animation speed by using a batch update of the collection view:
[self.collectionView performBatchUpdates:^{
[self.collectionView.viewForBaselineLayout.layer setSpeed:0.2];
[self.collectionView insertItemsAtIndexPaths: insertedIndexPaths];
} completion:^(BOOL finished) {
[self.collectionView.viewForBaselineLayout.layer setSpeed:1];
}];

After trying [CATransaction setAnimationDuration:] and [UIView setAnimationDuration:] in every possible phase of the layout process without success, I figured out a somewhat hacky way to change the duration of cell animations created by UICollectionView that doesn't rely on private API's.
You can use CALayer's speed property to change the relative media timing of animations performed on a given layer. For this to work with UICollectionView, you can change layer.speed to something less than 1 on the cell's layer. Obviously it's not great to have the cell's layer ALWAYS have a non-unity animation speed, so one option is to dispatch an NSNotification when preparing for cell animations, to which your cells subscribe, that will change the layer speed, and then change it back at an appropriate time after the animations are finished.
I don't recommend using this approach as a long-term solution as it's pretty roundabout, but it does work. Hopefully Apple will expose more options for UICollectionView animations in the future.

UICollectionView initiates all animations internally using some hardcoded value. However, you can always override that value until animations are committed.
In general, process looks like this:
begin animations
fetch all layout attribues
apply attributes to views (UICollectionViewCell's)
commit animations
applying attributes is done under each UICollectionViewCell and you can override animationDuration in appropriate method. The problem is that UICollectionViewCell has public method applyLayoutAttributes: BUT it's default implementation is empty!. Basically, UICollectionViewCell has other private method called _setLayoutAttributes: and this private method is called by UICollectionView and this private method calls applyLayoutAttributes: at the end. Default layout attributes, like frame, position, transform are applied with current animationDuration before applyLayoutAttributes: is called.
That said, you have to override animationDuration in private method _setLayoutAttributes:
- (void) _setLayoutAttributes:(PSTCollectionViewLayoutAttributes *)layoutAttributes
{
[UIView setAnimationDuration:3.0];
[super _setLayoutAttributes:layoutAttributes];
}
This is obviously, not applestore-safe. You can use one of those runtime hacks to override this private method safely.

You can set the layer's speed property (like in Rotoava's Answer) to change the control the speed of the animation. The problem is you are using arbitrary values because you do not know the actual duration of the insertion animation.
Using this post you can figure out what the default animation duration is.
newAnimationDuration = (1/layer.speed)*originalAnimationDuration
layer.speed = originalAnimationDuration/newAnimationDuration
If you wanted to make the animation 400ms long, in your layout you would:
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath];
//set attributes here
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
CGFloat originalAnimationDuration = [CATransaction animationDuration];
CGFloat newAnimationDuration = 0.4f;
cell.layer.speed = originalAnimationDuration/newAnimationDuration;
return attributes;
}
In my case I had cells which could be dragged off screen and I wanted to change the duration of the deletion animation based on the speed of the pan gesture.
In the gesture recognizer (which should be part of your collection view):
- (void)handlePanGesture:(UIPanGestureRecognizer *)sender
{
CGPoint dragVelocityVector = [sender velocityInView:self.collectionView];
CGFloat dragVelocity = sqrt(dragVelocityVector.x*dragVelocityVector.x + dragVelocityVector.y*dragVelocityVector.y);
switch (sender.state) {
...
case UIGestureRecognizerStateChanged:{
CustomLayoutClass *layout = (CustomLayoutClass *)self.collectionViewLayout;
layout.dragSpeed = fabs(dragVelocity);
...
}
...
}
Then in your customLayout:
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath];
CGFloat animationDistance = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
CGFloat originalAnimationDuration = [CATransaction animationDuration];
CGFloat newAnimationDuration = animationDistance/self.dragSpeed;
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
cell.layer.speed = originalAnimationDuration/newAnimationDuration;
return attributes;
}

Without subclassing:
[UIView animateWithDuration:2.0 animations:^{
[self.collection reloadSections:indexSet];
}];

An update to #AshleyMills since forBaselineLayout is deprecated
This works
self.collectionView.performBatchUpdates({ () -> Void in
let indexSet = IndexSet(0...(numberOfSections - 1))
self.collectionView.insertSections(indexSet)
self.collectionView.forFirstBaselineLayout.layer.speed = 0.5
}, completion: { (finished) -> Void in
self.collectionView.forFirstBaselineLayout.layer.speed = 1.0
})

You can change UICollectionView layout.speed property, that should change animation duration of your layout...

Related

UIScrollView paging with animation

I have achieved the following using UIScrollView and enabled paging.
I want the centre element of the scrollview to show little bigger than other elements. Need to increase/decrease the font of the text label as the scroll view is scrolling depending on its location.
I tried using transform but hard luck.
Code for adding the label's:
array = [[NSMutableArray alloc] init];
for(int i=0;i<10;i++){
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(x, 0, 150, 50)];
label.text = [NSString stringWithFormat:#"ID - %d",i];
label.textAlignment = UITextAlignmentCenter;
x +=150;
[self.scrollView addSubview:label];
[array addObject:label];
}
[self.scrollView setContentSize:CGSizeMake(x, 50)];
Animation which I performed in ScollViewDidScroll
float position = label.center.x - scrollView.contentOffset.x;
float offset = 2.0 - (fabs(scrollView.center.x - position) * 1.0) / scrollView.center.x;
label.transform = CGAffineTransformIdentity;
label.transform = CGAffineTransformScale(label.transform,offset, offset);
CODE: What I have achieved till now:
https://www.dropbox.com/s/h2q4qvg3n4fi34f/ScrollViewPagingPeeking.zip?dl=0
Don't use scrollview, used UICollectionView and Make collectionView cell bigger as screen size.
And Enable it's paging property than used it's delegate methods.
It's more preferable and efficient way of paging.
It will work using a collection view. Return layout attributes where the transform scale is calculated according to the position. Subclass the UICollectionViewLayout you are using (probably UICollectionViewFlowLayout). To get the resizing based on position to work you should override a few of its methods thusly:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *array = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attributes in array)
{
[self updateLayoutAttributesForItemAtIndexPath:layoutAttributes];
}
return array;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *layoutAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
[self updateLayoutAttributesForItemAtIndexPath:layoutAttributes];
}
- (UICollectionViewLayoutAttributes *)updateLayoutAttributesScaleForPosition:(UICollectionViewLayoutAttributes *)layoutAttributes
{
// NOTE: Your code assigning an updated transform scale based on the cell position here
return layoutAttributes;
}
Probably the main thing you were missing was the override to the shouldInvalidateLayoutForBoundsChange: method

Animate UITableView index, a la Apple Music?

Simple question:
Apple Music's table views only show an A-Z section index on the right edge once they've been scrolled down past a certain threshold, and that index animates in and out nicely.
I've been able to trigger the appearing / disappearing behaviour with the code below, but the index just pops in and out, there's no animation, and I can't find any way to get one to show up.
func sectionIndexTitlesForTableView(tableView: UITableView) -> [AnyObject]! {
if tableView.contentOffset.y > 88 {
return DataManager.sharedManager.frc!.sectionIndexTitles
} else {
return []
}
}
func scrollViewDidScroll(scrollView: UIScrollView) {
tableView.reloadSectionIndexTitles()
}
This basically means each time the scroll view ticks, it'll reload the section indexes, then conditionally hide or show the index based on the offset of the table. As I say, it works, but it doesn't animate the index, and I'd really love that functionality if possible.
Seems like it is not possible to do that, ate least apple doesn't provide any API to animate the section index view. I am able to slide in/out the indexes, but it doesn't resize the cells' contentView.
When you animate the section index view, right after the animation it comes back to its initial position. So I am basically setting the color to clearColor/black when hidden/visible.
I am not sure if apple approves this code since it's kind of using undocumented APIs
- (UIView *)indexTitlesView {
NSArray *subViews = [self.tableView subviews];
for (UIView *view in subViews) {
if ([NSStringFromClass([view class]) isEqualToString:#"UITableViewIndex"]) {
return view;
}
}
return nil;
}
- (void)slideIndexTitlesViewIn {
UIView *indexTitlesView = [self indexTitlesView];
CABasicAnimation *contentPositionAnimation = [CABasicAnimation animationWithKeyPath:#"position.x"];
contentPositionAnimation.fromValue = #(CGRectGetWidth(indexTitlesView.frame));
contentPositionAnimation.toValue = #(0);
contentPositionAnimation.additive = YES;
contentPositionAnimation.duration = 0.3;
contentPositionAnimation.delegate = self;
contentPositionAnimation.removedOnCompletion = NO;
contentPositionAnimation.fillMode = kCAFillModeForwards;
[indexTitlesView.layer removeAllAnimations];
[indexTitlesView.layer addAnimation:contentPositionAnimation forKey:#"slideInAnimation"];
self.tableView.sectionIndexColor = [UIColor blackColor];
}
- (void)slideIndexTitlesViewOut {
UIView *indexTitlesView = [self indexTitlesView];
CABasicAnimation *contentPositionAnimation = [CABasicAnimation animationWithKeyPath:#"position.x"];
contentPositionAnimation.fromValue = #(0);
contentPositionAnimation.toValue = #(CGRectGetWidth(indexTitlesView.frame));
contentPositionAnimation.additive = YES;
contentPositionAnimation.duration = 0.3;
contentPositionAnimation.delegate = self;
contentPositionAnimation.removedOnCompletion = NO;
contentPositionAnimation.fillMode = kCAFillModeForwards;
[indexTitlesView.layer removeAllAnimations];
[indexTitlesView.layer addAnimation:contentPositionAnimation forKey:#"slideOutAnimation"];
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
UIView *indexTitlesView = [self indexTitlesView];
NSArray *keys = [indexTitlesView.layer animationKeys];
for (NSString *key in keys) {
if ([indexTitlesView.layer animationForKey:key] == anim) {
if ([key isEqualToString:#"slideOutAnimation"] && flag == YES) {
self.tableView.sectionIndexColor = [UIColor clearColor];
}
[indexTitlesView.layer removeAllAnimations];
return;
}
}
}
Just use the animateWithDuration() method set the alpha value to zero and then animate it in by setting the value to 1
I would not suggest playing with private classes, since it can easily break in a future OS release.
I've implemented a custom control for the exact purpose. It mimics the native table index appearance while providing much more customization capabilities.
Check https://github.com/mindz-eye/MYTableViewIndex for details.

UICollectionView animate cells in initially

I present a view controller with a uicollectionview. I want the cells to animate in from the top.
My layout is a sublclass of flowlayout, so I override this method:
- (UICollectionViewLayoutAttributes *) initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:indexPath];
CGFloat height = [self collectionViewContentSize].height;
attributes.transform3D = CATransform3DMakeTranslation(0, -height, 0);
return attributes;
}
It almost works, but i see the cells appear (flash) momentarily on the screen before they animate in.
Any ideas why they appear in their final location before the transform is applied, and how I might prevent this?
Rather than setting the transform, change the center:
- (UICollectionViewLayoutAttributes *) initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:indexPath];
CGPoint c = attributes.center;
c.y -= self.collectionViewContentSize.height;
attributes.center = c;
return attributes;
}
Changing the transform can always cause problems with determining visibility, because doing so invalidates the frame property.

zoom entire UICollectionView

I have an iPad App where I'm using a UICollectionView and each UICollectionViewCell contains just a single UIImage.
Currently I'm displaying per 9 UIImages (3 rows * 3 columns) per page, I have several pages.
I would like to use Pinch Gesture to zoom on the entire UICollectionView to increase/decrease the number of row/columns displayed per page and the best would be to have beautiful zoom animation during the Pinch gesture!
Currently, I have added a Pinch Gesture on my UICollectionView. I catch the Pinch Gesture event to compute the number of rows/columns using the scale factor, if it has changed then I update the full UICollectionView using:
[_theCollectionView performBatchUpdates:^{
[_theCollectionView deleteSections:[NSIndexSet indexSetWithIndex:0]];
[_theCollectionView insertSections:[NSIndexSet indexSetWithIndex:0]];
} completion:nil];
It works but I don't have smooth animation during the transition.
Any idea? UICollectionView inherits from UIScrollView, is there a possibility to re-use the UIScrollView Pinch gesture feature to reach my goal?
I'm assuming you're using the default UICollectionViewDelegateFlowLayout, right? Then make sure you respond accordingly to the delegate methods, and when the pinch gesture occurs, simply invalidate the layout.
For example, if I want to adjust the size of every item, while pinching:
#interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
#property (nonatomic,assign) CGFloat scale;
#property (nonatomic,weak) IBOutlet UICollectionView *collectionView;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.scale = 1.0;
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"cell"];
UIPinchGestureRecognizer *gesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(didReceivePinchGesture:)];
[self.collectionView addGestureRecognizer:gesture];
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(50*self.scale, 50*self.scale);
}
- (void)didReceivePinchGesture:(UIPinchGestureRecognizer*)gesture
{
static CGFloat scaleStart;
if (gesture.state == UIGestureRecognizerStateBegan)
{
scaleStart = self.scale;
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
self.scale = scaleStart * gesture.scale;
[self.collectionView.collectionViewLayout invalidateLayout];
}
}
The property self.scale is just for show, you can apply this same concept to any other attribute, this doesn't require a beginUpdates/endUpdates because the user himself is carrying the timing of the scale.
Here's a running project, in case you want to see it in action.
Sorry for my 2 cents question, I have found the solution, very simple.
In my PinchGesture callback I have just done the following:
void (^animateChangeWidth)() = ^() {
_theFlowLayout.itemSize = cellSize;
};
[UIView transitionWithView:self.theCollectionView
duration:0.1f
options:UIViewAnimationOptionCurveLinear
animations:animateChangeWidth
completion:nil];
All cells of my UICollectionView are successfully changed and with a nice transition.
For Xamarin.iOS developers I found this solution: add a UIScrollView element to the main view and add the UICollectionView as an element of the UIScrollView. Then create a zoom delegate for the UIScrollView.
MainScrollView = new UIScrollView(new CGRect(View.Frame.X, View.Frame.Y, size.Width, size.Height));
_cellReuseId = GenCellReuseId();
_contentScroll = new UICollectionView(new CGRect(View.Frame.X, View.Frame.Y, size.Width, size.Height), new InfiniteScrollCollectionLayout(size.Width, size.Height));
_contentScroll.AllowsSelection = true;
_contentScroll.ReloadData();
_contentScroll.Center = MainScrollView.Center;
_contentScroll.Frame = new CGRect(_contentScroll.Frame.X, _contentScroll.Frame.Y - 32, _contentScroll.Frame.Width, _contentScroll.Frame.Height);
MainScrollView.ContentSize = _contentScroll.ContentSize;
MainScrollView.AddSubview(_contentScroll);
MainScrollView.MaximumZoomScale = 4f;
MainScrollView.MinimumZoomScale = 1f;
MainScrollView.BouncesZoom = true;
MainScrollView.ViewForZoomingInScrollView += (UIScrollView sv) =>
{
if (_contentScroll.Frame.Height < sv.Frame.Height && _contentScroll.Frame.Width < sv.Frame.Width)
{
_contentScroll.Center = MainScrollView.Center;
_contentScroll.Frame = new CGRect(_contentScroll.Frame.X, _contentScroll.Frame.Y - 64, _contentScroll.Frame.Width, _contentScroll.Frame.Height);
_contentScroll.BouncesZoom = true;
_contentScroll.AlwaysBounceHorizontal = false;
}
return _contentScroll;
};

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} …
… 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.
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
Swift 4.2 subclass:
class RotatableCollectionViewFlowLayout: UICollectionViewFlowLayout {
private var focusedIndexPath: IndexPath?
override func prepare(forAnimatedBoundsChange oldBounds: CGRect) {
super.prepare(forAnimatedBoundsChange: oldBounds)
focusedIndexPath = collectionView?.indexPathsForVisibleItems.first
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
guard let indexPath = focusedIndexPath
, let attributes = layoutAttributesForItem(at: indexPath)
, let collectionView = collectionView else {
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset)
}
return CGPoint(x: attributes.frame.origin.x - collectionView.contentInset.left,
y: attributes.frame.origin.y - collectionView.contentInset.top)
}
override func finalizeAnimatedBoundsChange() {
super.finalizeAnimatedBoundsChange()
focusedIndexPath = nil
}
}
For those using iOS 8+, willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are deprecated.
You should use the following now:
/*
This method is called when the view controller's view's size is changed by its parent (i.e. for the root view controller when its window rotates or is resized).
If you override this method, you should either call super to propagate the change to children or manually forward the change to children.
*/
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// Update scroll position during rotation animation
self.collectionView.contentOffset = (CGPoint){contentOffsetX, contentOffsetY};
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// Whatever you want to do when the rotation animation is done
}];
}
Swift 3:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (context:UIViewControllerTransitionCoordinatorContext) in
// Update scroll position during rotation animation
}) { (context:UIViewControllerTransitionCoordinatorContext) in
// Whatever you want to do when the rotation animation is done
}
}
I think the correct solution is to override targetContentOffsetForProposedContentOffset: method in a subclassed UICollectionViewFlowLayout
From the docs:
During layout updates, or when transitioning between layouts, the
collection view calls this method to give you the opportunity to
change the proposed content offset to use at the end of the animation.
You might override this method if the animations or transition might
cause items to be positioned in a way that is not optimal for your
design.
To piggy back off troppoli's solution you can set the offset in your custom class without having to worry about remembering to implement the code in your view controller. prepareForAnimatedBoundsChange should get called when you rotate the device then finalizeAnimatedBoundsChange after its done rotating.
#interface OrientationFlowLayout ()
#property (strong)NSIndexPath* pathForFocusItem;
#end
#implementation OrientationFlowLayout
- (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];
}
}
- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds {
[super prepareForAnimatedBoundsChange:oldBounds];
self.pathForFocusItem = [[self.collectionView indexPathsForVisibleItems] firstObject];
}
- (void)finalizeAnimatedBoundsChange {
[super finalizeAnimatedBoundsChange];
self.pathForFocusItem = nil;
}
#end
This problem bothered me for a bit as well. The highest voted answered seemed a bit too hacky for me so I just dumbed it down a bit and just change the alpha of the collection view respectively before and after rotation. I also don't animate the content offset update.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
self.collectionView.alpha = 0;
[self.collectionView.collectionViewLayout invalidateLayout];
self.scrollPositionBeforeRotation = CGPointMake(self.collectionView.contentOffset.x / self.collectionView.contentSize.width,
self.collectionView.contentOffset.y / self.collectionView.contentSize.height);
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
CGPoint newContentOffset = CGPointMake(self.scrollPositionBeforeRotation.x * self.collectionView.contentSize.width,
self.scrollPositionBeforeRotation.y * self.collectionView.contentSize.height);
[self.collectionView setContentOffset:newContentOffset animated:NO];
self.collectionView.alpha = 1;
}
Fairly smooth and less hacky.
I use a variant of fz. answer (iOS 7 & 8) :
Before rotation :
Store the current visible index path
Create a snapshot of the collectionView
Put an UIImageView with it on top of the collection view
After rotation :
Scroll to the stored index
Remove the image view.
#property (nonatomic) NSIndexPath *indexPath;
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
self.indexPathAfterRotation = [[self.collectionView indexPathsForVisibleItems] firstObject];
// Creates a temporary imageView that will occupy the full screen and rotate.
UIGraphicsBeginImageContextWithOptions(self.collectionView.bounds.size, YES, 0);
[self.collectionView drawViewHierarchyInRect:self.collectionView.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[imageView setFrame:[self.collectionView bounds]];
[imageView setTag:kTemporaryImageTag];
[imageView setBackgroundColor:[UIColor blackColor]];
[imageView setContentMode:UIViewContentModeCenter];
[imageView setAutoresizingMask:0xff];
[self.view insertSubview:imageView aboveSubview:self.collectionView];
[[self.collectionView collectionViewLayout] invalidateLayout];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[self.collectionView scrollToItemAtIndexPath:self.indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
[[self.view viewWithTag:kTemporaryImageTag] removeFromSuperview];
}
After rotate interface orientation the UICollectionViewCell usually move to another position, because we won't update contentSize and contentOffset.
So the visible UICollectionViewCell always not locate at expected position.
The visible UICollectionView which we expected image as follow
Orientation which we expected
UICollectionView must delegate the function [collectionView sizeForItemAtIndexPath] of『UICollectionViewDelegateFlowLayout』.
And you should calculate the item Size in this function.
The custom UICollectionViewFlowLayout must override the functions as follow.
-(void)prepareLayout
. Set itemSize, scrollDirection and others.
-(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
. Calculate page number or calculate visible content offset.
-(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
. Return visual content offset.
-(CGSize)collectionViewContentSize
. Return the total content size of collectionView.
Your viewController must override 『willRotateToInterfaceOrientation』and in this function
you should call the function [XXXCollectionVew.collectionViewLayout invalidateLayout];
But 『willRotateToInterfaceOrientation』 is deprecated in iOS 9, or you could call the function [XXXCollectionVew.collectionViewLayout invalidateLayout] in difference way.
There's an example as follow :
https://github.com/bcbod2002/CollectionViewRotationTest
in Swift 3.
you should track which cell item(Page) is being presented before rotate by indexPath.item, the x coordinate or something else.
Then, in your UICollectionView:
override func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
let page:CGFloat = pageNumber // your tracked page number eg. 1.0
return CGPoint(x: page * collectionView.frame.size.width, y: -(topInset))
//the 'y' value would be '0' if you don't have any top EdgeInset
}
In my case I invalidate the layout in viewDidLayoutSubviews() so the collectionView.frame.size.width is the width of the collectionVC's view that has been rotated.
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];
}];
}
If found that using targetContentOffsetForProposedContentOffset does not work in all scenarios and the problem with using didRotateFromInterfaceOrientation is that it gives visual artifacts. My perfectly working code is as follows:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
_indexPathOfFirstCell = [self indexPathsForVisibleItems].firstObject;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
if (_indexPathOfFirstCell) {
[UIView performWithoutAnimation:^{
[self scrollToItemAtIndexPath:self->_indexPathOfFirstCell atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
}];
_indexPathOfFirstCell = nil;
}
}
The key is to use the willRotateToInterfaceOrientation method to determine the part in the view that you want to scroll to and willAnimationRotationToInterfaceOrientation to recalculate it when the view has changed its size (the bounds have already changed when this method is called by the framework) and to actually scroll to the new position without animation. In my code I used the index path for the first visual cell to do that, but a percentage of contentOffset.y/contentSize.height would also do the job in slightly different way.
What does the job for me is this:
Set the size of your my cells from your my UICollectionViewDelegateFlowLayout method
func collectionView(collectionView: UICollectionView!, layout collectionViewLayout: UICollectionViewLayout!, sizeForItemAtIndexPath indexPath: NSIndexPath!) -> CGSize
{
return collectionView.bounds.size
}
After that I implement willRotateToInterfaceOrientationToInterfaceOrientation:duration: like this
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval)
{
let currentPage = Int(collectionView.contentOffset.x / collectionView.bounds.size.width)
var width = collectionView.bounds.size.height
UIView.animateWithDuration(duration) {
self.collectionView.setContentOffset(CGPointMake(width * CGFloat(currentPage), 0.0), animated: false)
self.collectionView.collectionViewLayout.invalidateLayout()
}
}
The above code is in Swift but you get the point and it's easy to "translate"
You might want to hide the collectionView during it's (incorrect) animation and show a placeholder view of the cell that rotates correctly instead.
For a simple photo gallery I found a way to do it that looks quite good. See my answer here:
How to rotate a UICollectionView similar to the photos app and keep the current view centered?
My way is to use a UICollectionViewFlowlayout object.
Set the ojbect line spacing if it scrolls horizontally.
[flowLayout setMinimumLineSpacing:26.0f];
Set its interitem spacing if it scrolls vertically.
[flowLayout setMinimumInteritemSpacing:0.0f];
Notice it behaves different when you rotate the screen. In my case, I have it scrolls horizontally so minimumlinespacing is 26.0f. Then it seems horrible when it rotates to landscape direction. I have to check rotation and set minimumlinespacing for that direction 0.0f to make it right.
That's it! Simple.
I had the issue with my project,i used two different layout for the UICollectionView.
mCustomCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"LandScapeCell" forIndexPath:indexPath];
theCustomCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"PortraitCell" forIndexPath:indexPath];
Then Check it for each orientation and use your configuration for each orientation.
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGSize pnt = CGSizeMake(70, 70);
return pnt; }
-(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
// UIEdgeInsetsMake(<#CGFloat top#>, <#CGFloat left#>, <#CGFloat bottom#>, <#CGFloat right#>)
return UIEdgeInsetsMake(3, 0, 3, 0); }
This way you can adjust the content offset and the size of your cell.
Use <CollectionViewDelegateFlowLayout> and in the method didRotateFromInterfaceOrientation: reload data of the CollectionView.
Implement collectionView:layout:sizeForItemAtIndexPath: method of <CollectionViewDelegateFlowLayout> and in the method verify the Interface orientation and apply your custom size of each cell.
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (UIInterfaceOrientationIsPortrait(orientation)) {
return CGSizeMake(CGFloat width, CGFloat height);
} else {
return CGSizeMake(CGFloat width, CGFloat height);
}
}
I have a similar case in which i use this
- (void)setFrame:(CGRect)frame
{
CGFloat currentWidth = [self frame].size.width;
CGFloat offsetModifier = [[self collectionView] contentOffset].x / currentWidth;
[super setFrame:frame];
CGFloat newWidth = [self frame].size.width;
[[self collectionView] setContentOffset:CGPointMake(offsetModifier * newWidth, 0.0f) animated:NO];
}
This is a view that contains a collectionView.
In the superview I also do this
- (void)setFrame:(CGRect)frame
{
UICollectionViewFlowLayout *collectionViewFlowLayout = (UICollectionViewFlowLayout *)[_collectionView collectionViewLayout];
[collectionViewFlowLayout setItemSize:frame.size];
[super setFrame:frame];
}
This is to adjust the cell sizes to be full screen (full view to be exact ;) ). If you do not do this here a lot of error messages may appear about that the cell size is bigger than the collectionview and that the behaviour for this is not defined and bla bla bla.....
These to methods can off course be merged into one subclass of the collectionview or in the view containing the collectionview but for my current project was this the logical way to go.
The "just snap" answer is the right approach and doesn't require extra smoothing with snapshot overlays IMO. However there's an issue which explains why some people see that the correct page isn't scrolled to in some cases. When calculating the page, you'd want to use the height and not the width. Why? Because the view geometry has already rotated by the time targetContentOffsetForProposedContentOffset is called, and so what was the width is now the height. Also rounding is more sensible than ceiling. So:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
NSInteger page = round(proposedContentOffset.x / self.collectionView.bounds.size.height);
return CGPointMake(page * self.collectionView.bounds.size.width, 0);
}
I solved this problem with Following Steps:
Calculate currently scrolled NSIndexPath
Disable Scrolling and Pagination in UICollectionView
Apply new Flow Layout to UICollectionView
Enable Scrolling and Pagination in UICollectionView
Scroll UICollectionView to current NSIndexPath
Here is the Code Template demonstrating the Above Steps:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration;
{
//Calculating Current IndexPath
CGRect visibleRect = (CGRect){.origin = self.yourCollectionView.contentOffset, .size = self.yourCollectionView.bounds.size};
CGPoint visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
self.currentIndexPath = [self.yourCollectionView indexPathForItemAtPoint:visiblePoint];
//Disable Scrolling and Pagination
[self disableScrolling];
//Applying New Flow Layout
[self setupNewFlowLayout];
//Enable Scrolling and Pagination
[self enableScrolling];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
//You can also call this at the End of `willRotate..` method.
//Scrolling UICollectionView to current Index Path
[self.yourCollectionView scrollToItemAtIndexPath:self.currentIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO];
}
- (void) disableScrolling
{
self.yourCollectionView.scrollEnabled = false;
self.yourCollectionView.pagingEnabled = false;
}
- (void) enableScrolling
{
self.yourCollectionView.scrollEnabled = true;
self.yourCollectionView.pagingEnabled = true;
}
- (void) setupNewFlowLayout
{
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc] init];
flowLayout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0);
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
flowLayout.minimumInteritemSpacing = 0;
flowLayout.minimumLineSpacing = 0;
[flowLayout setItemSize:CGSizeMake(EXPECTED_WIDTH, EXPECTED_HEIGHT)];
[self.yourCollectionView setCollectionViewLayout:flowLayout animated:YES];
[self.yourCollectionView.collectionViewLayout invalidateLayout];
}
I hope this helps.
I had got some troubles with animateAlongsideTransition block in animateAlongsideTransition (see the code below).
Pay attention, that it is called during (but not before) the animation
My task was update the table view scroll position using scrolling to the top visible row (I’ve faced with the problem on iPad when table view cells shifted up when the device rotation, therefore I was founding the solution for that problem). But may be it would be useful for contentOffset too.
I tried to solve the problem by the following way:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
__weak TVChannelsListTableViewController *weakSelf = self;
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
weakSelf.topVisibleRowIndexPath = [[weakSelf.tableView indexPathsForVisibleRows] firstObject];
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[weakSelf.tableView scrollToRowAtIndexPath:weakSelf.topVisibleRowIndexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
}];
}
But it didn’t work. For instance, index path of the top cel was (0, 20). But when the device rotation animateAlongsideTransition block was called and [[weakSelf.tableView indexPathsForVisibleRows] firstObject] returned index path (0, 27).
I thought the problem was in retrieving index paths to weakSelf. Therefore to solve the problem I’ve moved self.topVisibleRowIndexPath before [coordinator animateAlongsideTransition: completion] method calling:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
__weak TVChannelsListTableViewController *weakSelf = self;
self.topVisibleRowIndexPath = [[weakSelf.tableView indexPathsForVisibleRows] firstObject];
[coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[weakSelf.tableView scrollToRowAtIndexPath:weakSelf.topVisibleRowIndexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
}];
}
And the other interesting thing that I’ve discovered is that the deprecated methods willRotateToInterfaceOrientation and willRotateToInterfaceOrientation are still successful called in iOS later 8.0 when method viewWillTransitionToSize is not redefined.
So the other way to solve the problem in my case was to use deprecated method instead of new one. I think it would be not right solution, but it is possible to try if other ways don’t work :)
You might want to try this untested code:
- (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation
duration: (NSTimeInterval) duration
{
[UIView animateWithDuration: duration
animation: ^(void)
{
CGPoint newContentOffset = CGPointMake(self.scrollPositionBeforeRotation.x *
self.collectionView.contentSize.height,
self.scrollPositionBeforeRotation.y *
self.collectionView.contentSize.width);
[self.collectionView setContentOffset: newContentOffset
animated: YES];
}];
}

Resources