Set anchor point in UICollectionViewLayoutAttributes - ios

I want to perform transform animation in UICollectionViewLayout. Well that is achieved easily but I can't find way to set the anchorPoint of UICollectionViewLayoutAttributes. I want to perform door opening and closing animation while collection view item is inserted
-(UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath{
UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
attributes.alpha = 1.0;
CGFloat progress = 0.0;
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0/500.0;
CGFloat angle = (1 - progress) * M_PI_2;
transform = CATransform3DRotate(transform, angle, 0, 1, 0 );
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:itemIndexPath];
cell.hidden = YES;
attributes.transform3D = transform;
return attributes;
}

There doesn't seem to be an anchorPoint attribute in UICollectionViewLayoutAttributes, so you'll have to subclass it, add a property for anchorPoint and use the applyLayoutAttributes on UICollectionViewCell to set the anchorPoint value on the cell.

Related

Animated custom layout in UICollectionView

I need to create a UICollectionView like the following picture.
I have been able to make horizontally scrollable, but unable to make the UI like the picture. Any help? Thanks.
Here is what you wanted,you just need to have a custom UICollectionViewFlowLayout,and override the method -(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect to change the cell display attributes.
Here is the layout code with comment in each main operation
#implementation HorizonSclaeLayout
-(instancetype)init{
self = [super init];
if (self) {
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
}
return self;
}
static const CGFloat kMaxDistancePercentage = 0.3f;
static const CGFloat kMaxRotation = 0;//(CGFloat)(70.0 * (M_PI / 180.0));
static const CGFloat kMaxZoom = 0.6f;
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
//1 the visible rectangle of the collection view, calculated using the content offset of the view and the bounds size.
CGRect visibleRect = (CGRect){
.origin = self.collectionView.contentOffset,
.size = self.collectionView.bounds.size
};
//2 the maximum distance away from the center, which defines the distance from the center at which each cell is fully rotated
CGFloat maxDistance = visibleRect.size.width/2;
NSArray *array = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attributes in array) {
//3 find the distance of the cell from the center of the current visible rectangle
CGFloat distance = CGRectGetMidX(visibleRect) - attributes.center.x;
//4 normalize this distance against the maximum distance to give a percentage of how far the view is along the line from the center to the maximum points in either direction
CGFloat normalizedDistance = distance / maxDistance;
normalizedDistance = MIN(normalizedDistance, 1.0f);
normalizedDistance = MAX(normalizedDistance, -1.0f);
//5 calculate the rotation and zoom
CGFloat rotation = normalizedDistance * kMaxRotation;
CGFloat zoom = 1.0f + ((1.0f - ABS(normalizedDistance))*kMaxZoom);
//6 create the required transform by first setting m34 so that when the rotation is done
// skew is applied to make it have the appearance of coming out of and going into the screen
CATransform3D transform = CATransform3DIdentity;
transform.m34 = - 1.0 / 550.0;
transform = CATransform3DRotate(transform, rotation, 0.0f, 1.0f, 0.0f);
transform = CATransform3DScale(transform, zoom, zoom, 0.0f);
attributes.transform3D = transform;
}
return array;
}
-(CGSize)itemSize{
return CGSizeMake(60, 60 * 1.2);
}
-(CGFloat)minimumLineSpacing{
return 30;
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return YES;
}
#end
Here in this sample LineLayout has already implemented the required custom layout (that's what I guessed from the image shown).

UITableviewcell animation only once

Here is my willDisplayCell animation code I want to animate only once, don't repeat when scroll up the tableview.
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
UIView *cellContentView = [cell contentView];
CGFloat rotationAngleDegrees = -30;
CGFloat rotationAngleRadians = rotationAngleDegrees * (M_PI/180);
CGPoint offsetPositioning = CGPointMake(0, cell.contentView.frame.size.height*4);
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, rotationAngleRadians, -50.0, 0.0, 1.0);
transform = CATransform3DTranslate(transform, offsetPositioning.x, offsetPositioning.y, -50.0);
cellContentView.layer.transform = transform;
cellContentView.layer.opacity = 0.8;
[UIView animateWithDuration:0.95 delay:00 usingSpringWithDamping:0.85 initialSpringVelocity:0.8 options:0 animations:^{
cellContentView.layer.transform = CATransform3DIdentity;
cellContentView.layer.opacity = 1;
} completion:^(BOOL finished) {}];
}
If you want any of your code to run only once, you can use dipatch_once. This will make sure that this code is run only once in life span of parent object. You can put that code inside dispatch_async if that code is being run from somewhere other than tableview's event handlers.
dispatch_async(dispatch_get_main_queue(),^{
static dispatch_once_t once;
dispatch_once(&once, ^{
//Your animation code.
});
};
In order to animate each cell only once each cell has to know that it animated itself and it shouldn't again. Easiest way to achieve that is to give the cell BOOL variable called animated to determine whether if or not the cell has already animated.
Create custom cell class
Create new Cocoa Class called for example CustomCell. It should subclass the UITableViewCell.
Inside CustomCell.h declare a BOOL property called animated:
#property (nonatomic) BOOL animated;
If you are using storyboards like me, remember to also update the class of a cell in storyboard editor, just like you do when you are creating new view controllers
That's it here.
Modify View Controller
Now you should import your new class and swap (UITableViewCell *) with (CustomCell *) in your tableViews delegate methods.
In willDisplayCell: add necessary logic, it will look like this:
-(void)tableView:(UITableView *)tableView willDisplayCell:(CustomCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if(!cell.animated){ //added
cell.animated = YES; //added
UIView *cellContentView = [cell contentView];
CGFloat rotationAngleDegrees = -30;
CGFloat rotationAngleRadians = rotationAngleDegrees * (M_PI/180);
CGPoint offsetPositioning = CGPointMake(0, cell.contentView.frame.size.height*4);
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, rotationAngleRadians, -50.0, 0.0, 1.0);
transform = CATransform3DTranslate(transform, offsetPositioning.x, offsetPositioning.y, -50.0);
cellContentView.layer.transform = transform;
cellContentView.layer.opacity = 0.8;
[UIView animateWithDuration:0.95 delay:00 usingSpringWithDamping:0.85 initialSpringVelocity:0.8 options:0 animations:^{
cellContentView.layer.transform = CATransform3DIdentity;
cellContentView.layer.opacity = 1;
} completion:^(BOOL finished) {}];
}
And this is it. Hope it works for you

UICollectionViewCell disappears in iOS 8

I have a collectionView which works well in iOS 7 and now in iOS 8 is acts strangely.
when collectionView appears it only displays one cell: (it must be 2)
but after scrolling a bit the second cell appears
Im using a custom collectionViewFlowLayout. but changing to UICollectionViewFlowLayout doesn't fix the issue.
Cell Size : 657, 500
Min Spacing For Lines : 100
Min Spacing For Cells : 10
I have added left and right edge insets: (if I remove the insets it works well. but I must use insets to keep my cell at the center of view)
- (UIEdgeInsets)collectionView:(UICollectionView *)cv
layout:(UICollectionViewLayout *)cvl
insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, (cv.bounds.size.width - 657) / 2.0f, 0,
(cv.bounds.size.width - 657) / 2.0f);
}
Here is my custom flow layout:
#import "CoverFlowLayout.h"
static const CGFloat kMaxDistancePercentage = 0.3f;
//static const CGFloat kMaxRotation = (CGFloat)(50.0 * (M_PI / 180.0));
static const CGFloat kMaxZoom = 0.1f;
#implementation CoverFlowLayout
- (id)init {
if ((self = [super init])) {
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.minimumLineSpacing = 10000.0f; }
return self;
}
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
// 1
CGRect visibleRect =
(CGRect){.origin = self.collectionView.contentOffset,
.size = self.collectionView.bounds.size};
CGFloat maxDistance =
visibleRect.size.width * kMaxDistancePercentage;
// 2
NSArray *array = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attributes in array) {
// 3
CGFloat distance =
CGRectGetMidX(visibleRect) - attributes.center.x;
// 4
CGFloat normalizedDistance = distance / maxDistance;
normalizedDistance = MIN(normalizedDistance, 1.0f);
normalizedDistance = MAX(normalizedDistance, -1.0f);
// 5
//CGFloat rotation = normalizedDistance * kMaxRotation;
CGFloat zoom = 1.0f + ((1.0f - ABS(normalizedDistance)) * kMaxZoom);
// 6
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -1000.0;
//transform = CATransform3DRotate(transform,
// rotation, 0.0f, 1.0f, 0.0f);
transform = CATransform3DScale(transform, zoom, zoom, zoom);
attributes.transform3D = transform;
}
// 7
return array;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
- (CGPoint)targetContentOffsetForProposedContentOffset: (CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
// 1
CGFloat offsetAdjustment = CGFLOAT_MAX;
CGFloat horizontalCenter = proposedContentOffset.x +
(CGRectGetWidth(self.collectionView.bounds) / 2.0f);
// 2
CGRect targetRect = CGRectMake(proposedContentOffset.x,
0.0f, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *array =
[super layoutAttributesForElementsInRect:targetRect];
for (UICollectionViewLayoutAttributes* layoutAttributes in array)
{
// 3
CGFloat distanceFromCenter = layoutAttributes.center.x - horizontalCenter;
if (ABS(distanceFromCenter) < ABS(offsetAdjustment))
{
offsetAdjustment = distanceFromCenter;
}
}
// 4
return CGPointMake(proposedContentOffset.x + offsetAdjustment,
proposedContentOffset.y);
}
initially in overrided layoutAttributesForElementsInRect visible rect is {0,0, 1024, 768}
but [super layoutAttributesForElementsInRect:rect]; returns only one UICollectionViewCellAttribute. (it should be 2)
is any idea how can I fix this?
I don know How it can be cause of the issue but it was originated from:
NSIndexPath *visibleIndexPath = [self.collectionView indexPathForItemAtPoint:midPoint];
I want to update my pageControl to indicate which cell is at the center of screen.
I Changed my method and now it works well:
//*****updating page control*****
// get the visible rect
CGRect visibleRect = (CGRect) {.origin = self.collectionView.contentOffset,
.size = self.collectionView.bounds.size};
// get the mid point in the visible rect
CGPoint midPoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
// find indexPath of the item in that midPoint
//in iOS 8 Cause the second cell disappear
//NSIndexPath *visibleIndexPath = [self.collectionView indexPathForItemAtPoint:midPoint];
//iterating through visble cells to find the cell which contains midpoint then get get that cell indexpath
for (UICollectionViewCell *cell in [self.collectionView visibleCells]) {
if (CGRectContainsPoint(cell.frame, midPoint)) {
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
//update page control
self.pageControl.currentPage = indexPath.row;
//quiting loop
break;
}
}

Transforming UICollectionViewCell causes touch events not be captured

Im trying to create a horizontal collection view by subclassing UICollectionViewFlowLayout.
which the center cell be scaled a little more to be focused on the view.
see the screen shot:
but UICollectionViewCell doesn't capture touch events. I mean when I tap on the cell the delegate method - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath won't called.
but when I remove the scale transform from the cell, the above method called!
#import "CoverFlowLayout.h"
static const CGFloat kMaxDistancePercentage = 0.3f;
static const CGFloat kMaxRotation = (CGFloat)(50.0 * (M_PI / 180.0));
static const CGFloat kMaxZoom = 0.1f;
#implementation CoverFlowLayout
- (id)init {
if ((self = [super init])) {
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.minimumLineSpacing = 10000.0f; }
return self;
}
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
// 1
CGRect visibleRect =
(CGRect){.origin = self.collectionView.contentOffset,
.size = self.collectionView.bounds.size};
CGFloat maxDistance =
visibleRect.size.width * kMaxDistancePercentage;
// 2
NSArray *array =
[super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attributes in array) {
// 3
CGFloat distance =
CGRectGetMidX(visibleRect) - attributes.center.x;
// 4
CGFloat normalizedDistance = distance / maxDistance;
normalizedDistance = MIN(normalizedDistance, 1.0f);
normalizedDistance = MAX(normalizedDistance, -1.0f);
// 5
CGFloat rotation = normalizedDistance * kMaxRotation;
CGFloat zoom = 1.0f + ((1.0f - ABS(normalizedDistance)) * kMaxZoom);
// 6
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -1000.0;
//transform = CATransform3DRotate(transform,
// rotation, 0.0f, 1.0f, 0.0f);
transform = CATransform3DScale(transform, zoom, zoom, 0.0f);
attributes.transform3D = transform;
}
// 7
return array;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
- (CGPoint)targetContentOffsetForProposedContentOffset: (CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
// 1
CGFloat offsetAdjustment = CGFLOAT_MAX;
CGFloat horizontalCenter = proposedContentOffset.x +
(CGRectGetWidth(self.collectionView.bounds) / 2.0f);
// 2
CGRect targetRect = CGRectMake(proposedContentOffset.x,
0.0f, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *array =
[super layoutAttributesForElementsInRect:targetRect];
for (UICollectionViewLayoutAttributes* layoutAttributes in array)
{
// 3
CGFloat distanceFromCenter = layoutAttributes.center.x - horizontalCenter;
if (ABS(distanceFromCenter) < ABS(offsetAdjustment))
{
offsetAdjustment = distanceFromCenter;
}
}
// 4
return CGPointMake(proposedContentOffset.x + offsetAdjustment,
proposedContentOffset.y);
}
any idea?
You don't need to scale on z-axis, the problem is caused because you were scaling on z to zero
The scale on z-axis should be greater than zero; Making it zero causes the button to have "no depth", so the touches are not recognized. (Although you can still see the button)
Scaling is a computed using a multiplication, so in order to cause "no transformation on z-axis", the value should be 1, rather than 0
I had to scale the cell along the Z axis too:
transform = CATransform3DScale(transform, zoom, zoom, zoom);
the cell touch events works.
does anyone knows why scaling along Z axis is necessary?

What is the initial x coordinate of a UITableViewCell?

In a UITableViewController, this function logs into the console the x coordinate of the tableview cells
- (void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
//1. Setup the CATransform3D structure
CATransform3D rotation;
rotation = CATransform3DMakeRotation( (45.0*M_PI)/180, 0.0, 0.7, 0.4);
rotation.m34 = 1.0/ -600;
//2. Define the initial state (Before the animation)
cell.layer.shadowColor = [[UIColor blackColor]CGColor];
cell.layer.shadowOffset = CGSizeMake(10, 10);
cell.alpha = 0;
cell.layer.transform = rotation;
cell.layer.anchorPoint = CGPointMake(0, 0.5);
//4. Define the final state (After the animation) and commit the animation
[UIView beginAnimations:#"rotation" context:NULL];
[UIView setAnimationDuration:0.3];
cell.layer.transform = CATransform3DIdentity;
cell.alpha = 1;
cell.layer.shadowOffset = CGSizeMake(0, 0);
[UIView commitAnimations];
NSLog(#"X%d= %d,Y%d=%d",(int)indexPath.row ,(int)cell.layer.position.x,(int)indexPath.row,(int)cell.layer.position.y);
}
it logs for the cells that are initially displayed (cell 0 to 12)
X0= 160,Y0=25
X1= 160,Y1=75
X2= 160,Y2=125
X3= 160,Y3=175
X4= 160,Y4=225
X5= 160,Y5=275
X6= 160,Y6=325
X7= 160,Y7=375
X8= 160,Y8=425
X9= 160,Y9=475
X10= 160,Y10=525
X11= 160,Y11=575
X12= 160,Y12=625
But after scrolling the tableview, it logs
X13= 0,Y13=675
X14= 0,Y14=725
X15= 0,Y15=775
X16= 0,Y16=825
and only cell 13's view is offset by 160 in the simulator
This could be fixed by adding to the previous method
if(cell.layer.position.x != 0){
cell.layer.position = CGPointMake(0, cell.layer.position.y);
}
What I am asking is why initially the x coordinate = 160 ???
The position of a layer denotes its center position, and not the top-left corner. you changed that behaviour through manipulation of the anchorPoint property. Insert the following line after the line that contains anchorPoint:
cell.layer.position = CGPointMake(0, cell.layer.position.y);
Changing the anchorPoint also changes the frame. To keep the frame the same, you have to do the opposite movement with the position property.
(To get the position of the top-left corner, you would use cell.frame.origin instead.)
See also: CALayer reference

Resources