UITableviewcell animation only once - ios

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

Related

UITableView WillDisplayCell Method call after particular time?

I am trying to put animation on UITableViewCell bottom to top which working fine code for that:
-(void)tableView:(UITableView *)tableView willDisplayCell:(audioVcCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
cell.alpha =0.0;
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:3.0 delay:indexPath.row+3 usingSpringWithDamping:0.85 initialSpringVelocity:0.8 options:UIViewAnimationOptionCurveEaseInOut animations:^{
cellContentView.layer.transform = CATransform3DIdentity;
cellContentView.layer.opacity = 1;
cell.alpha=1.0;
} completion:^(BOOL finished) {
}];
}
Here I want to display each cell one by one with gap of 2 or 3 seconds. Currently Cell will display quickly. Thanks in advance.
I solved it by the idea given by #PhaniRaghu I made some changes in his answer and it worked fine.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, indexPath.row * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:3.0
delay:indexPath.row
usingSpringWithDamping:0.85
initialSpringVelocity:0.8
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
cellContentView.layer.transform = CATransform3DIdentity;
cellContentView.layer.opacity = 1;
cell.alpha=1.0;
} completion:^(BOOL finished) {
}];
});

How to chain CGAffineTransform together in separate animations?

I need two animations on a UIView:
Make the view move down and slightly grow.
Make the view grow even bigger about its new center.
When I attempt to do that, the second animation starts in a weird location but ends up in the right location and size. How would I make the second animation start at the same position that the first animation ended in?
#import "ViewController.h"
static const CGFloat kStartX = 100.0;
static const CGFloat kStartY = 20.0;
static const CGFloat kStartSize = 30.0;
static const CGFloat kEndCenterY = 200.0;
#interface ViewController ()
#property (nonatomic, strong) UIView *box;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.box = [[UIView alloc] initWithFrame:CGRectMake(kStartX, kStartY, kStartSize, kStartSize)];
self.box.backgroundColor = [UIColor brownColor];
[self.view addSubview:self.box];
[UIView animateWithDuration:2.0
delay:1.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:0
animations:^{
self.box.transform = [self _transformForSize:50.0 centerY:kEndCenterY];
}
completion:^(BOOL finished) {
[UIView animateWithDuration:2.0
delay:1.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:0
animations:^{
self.box.transform = [self _transformForSize:100.0 centerY:kEndCenterY];
}
completion:^(BOOL finished) {
}];
}];
}
- (CGAffineTransform)_transformForSize:(CGFloat)newSize centerY:(CGFloat)newCenterY
{
CGFloat newScale = newSize / kStartSize;
CGFloat startCenterY = kStartY + kStartSize / 2.0;
CGFloat deltaY = newCenterY - startCenterY;
CGAffineTransform translation = CGAffineTransformMakeTranslation(0.0, deltaY);
CGAffineTransform scaling = CGAffineTransformMakeScale(newScale, newScale);
return CGAffineTransformConcat(scaling, translation);
}
#end
There's one caveat: I'm forced to use setTransform rather than setFrame. I'm not using a brown box in my real code. My real code is using a complex UIView subclass that doesn't scale smoothly when I use setFrame.
This looks like it might be a UIKit bug with how UIViews resolve their layout when you apply a transform on top of an existing one. I was able to at least get the starting coordinates for the second animation correct by doing the following, at the very beginning of the second completion block:
[UIView animateWithDuration:2.0
delay:1.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:0
animations:^{
self.box.transform = [self _transformForSize:50.0 centerY:kEndCenterY];
}
completion:^(BOOL finished) {
// <new code here>
CGRect newFrame = self.box.frame;
self.box.transform = CGAffineTransformIdentity;
self.box.frame = newFrame;
// </new code>
[UIView animateWithDuration:2.0
delay:1.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:0
animations:^{
self.box.transform = [self _transformForSize:100.0 centerY:kEndCenterY];
}
completion:^(BOOL finished) {
}];
}];
Using the same call to -_transformForSize:centerY: results in the same Y translation being performed in the second animation, though, so the box ends up further down in the view than you want when all is said and done.
To fix this, you need to calculate deltaY based on the box's starting Y coordinate at the end of the first animation rather than the original, constant Y coordinate:
- (CGAffineTransform)_transformForSize:(CGFloat)newSize centerY:(CGFloat)newCenterY
{
CGFloat newScale = newSize / kStartSize;
// Replace this line:
CGFloat startCenterY = kStartY + kStartSize / 2.0;
// With this one:
CGFloat startCenterY = self.box.frame.origin.y + self.box.frame.size.height / 2.0;
// </replace>
CGFloat deltaY = newCenterY - startCenterY;
CGAffineTransform translation = CGAffineTransformMakeTranslation(0.0, deltaY);
CGAffineTransform scaling = CGAffineTransformMakeScale(newScale, newScale);
return CGAffineTransformConcat(scaling, translation);
}
and it should do the trick.
UPDATE
I should say, I consider this "trick" more of a work-around than an actual solution, but the box's frame and transform look correct at each stage of the animation pipeline, so if there's a true "solution" it's eluding me at the moment. This trick at least solves the translation problem for you, so you can experiment with your more complex view hierarchy and see how far you get.

UITableViewCell animation breaks UITableView touch detection and scrolling

I was trying to achieve similar to google+ animation on showing new UITableViewCells when they appear for the first time.
This is what I'm doing:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if([[self serverFetchControllerForTableView:tableView] hasDataAtIndexPath:indexPath])
{
if (![[self shownIndexesForTableView:tableView] containsObject:indexPath]) {
[[self shownIndexesForTableView:tableView] addObject:indexPath];
UIView* view = [cell contentView];
view.layer.transform = self.initialTransformation;
view.layer.opacity = 0.0;
[UIView animateWithDuration:ANIMATION_CELL_APPEARANCE_TIME animations:^{
view.layer.transform = CATransform3DIdentity;
view.layer.opacity = 1;
}];
}
}
}
//initial transformation looks like that
CGFloat rotationAngleRadians = ANIMATION_CELL_APPEARANCE_ANGLE_DEG * (M_PI/180);
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, rotationAngleRadians, 1.0, 0.0, 1.0);
transform = CATransform3DTranslate(transform, ANIMATION_CELL_APPEARANCE_X, ANIMATION_CELL_APPEARANCE_Y, 0.0);
self.initialTransformation = transform;
Everything works in terms of visuals, but while those cells are appearing, I'm losing complete control of UITableView scrolling - I cannot touch cells, stop scrolling or select any of them, until animation is finished.
Does anyone have any suggestion what I can do better here to fix that problem ?
Same problem can be found here:
http://www.raywenderlich.com/49311/advanced-table-view-animations-tutorial-drop-in-cards
Try putting time to 3.0seconds in TipInCellAnimator any for 3.0 control is lost completely.
This is the normal behavior for animations using one of the animateWithDuration... methods. If you want user interaction during the animation, try using
animateWithDuration:delay:options:animations:completion: and passing UIViewAnimationOptionAllowUserInteraction as the option. I don't know what that might do to your animation, but it should allow user interaction.

How to do animation like app launch when you tap on the app icon on iOS homescreen?

I wants to do animation something similar to app launch on the ios home screen. Like the whole collection view goes to scale up and the launched app covered the whole screen.
I am using the iOS 7 new api for viewcontroller transitions.
And I am using the parent collection viewcontroller snapshot to appropriate animation.
But still I am not getting enough like what animation is actually is happening at that time?
To get the performance and look you want, you may have to perform transforms on the view layers.
I put up a little demo on GitHub, but the relevant code is below.
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCellView *cell = (MyCellView *)[self collectionView:collectionView cellForItemAtIndexPath:indexPath];
self.detailViewController = [[DetailViewController alloc] initWithNibName:nil bundle:nil];
self.detailViewController.labelString = [NSString stringWithFormat:#"%i", indexPath.row];
[self.view.superview addSubview:self.detailViewController.view];
// tap position relative to collection view
float screenX = self.collectionView.frame.origin.x + cell.center.x;
float screenY = self.collectionView.frame.origin.y + cell.center.y - self.collectionView.contentOffset.y;
// tap position relative to view frame
float translateX = (self.view.frame.size.width / -2.0) + screenX;
float translateY = (self.view.frame.size.height / -2.0) + screenY;
CATransform3D transform_detail = CATransform3DScale(CATransform3DMakeTranslation(translateX, translateY, 0.0), 0.0, 0.0, 0.0);
CATransform3D transform_main = CATransform3DScale(CATransform3DMakeTranslation(-translateX * 5.0, -translateY * 5.0, 0.0), 5.0, 5.0, 5.0);
self.detailViewController.view.layer.transform = transform_detail;
[UIView animateWithDuration:0.5 animations:^{
self.detailViewController.view.layer.transform = CATransform3DIdentity;
self.view.layer.transform = transform_main;
} completion:^(BOOL finished) {
self.view.layer.transform = CATransform3DIdentity;
}];
}
You need to use custom segues.
This tutorial should help you and below is the code excerpt.
- (void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
// Add the destination view as a subview, temporarily
[sourceViewController.view addSubview:destinationViewController.view];
// Transformation start scale
destinationViewController.view.transform = CGAffineTransformMakeScale(0.05, 0.05);
// Store original centre point of the destination view
CGPoint originalCenter = destinationViewController.view.center;
// Set center to start point of the button
destinationViewController.view.center = self.originatingPoint;
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
// Grow!
destinationViewController.view.transform = CGAffineTransformMakeScale(1.0, 1.0);
destinationViewController.view.center = originalCenter;
}
completion:^(BOOL finished){
[destinationViewController.view removeFromSuperview]; // remove from temp super view
[sourceViewController presentViewController:destinationViewController animated:NO completion:NULL]; // present VC
}];
}

Adding a new UITableViewRow pushes the existing cells down too quickly

I am trying to create UITableCellView animation like Peek Calendar. As can be seen here:
I am using UITableView and the following code for animation
//Animaiton for the table view cells to appear
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if([cell.reuseIdentifier isEqualToString:#"HolidayCell"]) {
int x = (indexPath.row-self.selectedMonth) %2;
//1. Setup the CATransform3D structure
CATransform3D rotation;
CATransform3D position;
position = CATransform3DMakeTranslation(0, -36, 0) ;
//rotation.m33 = 12;
UILabel *shadow = (UILabel*)[cell.contentView viewWithTag:2];
//2. Define the initial state (Before the animation)
cell.alpha = 1;
shadow.alpha = 0.75;
if(x==1) {
rotation = CATransform3DMakeRotation((-90.0*M_PI)/180, 1.5, 0.0, 0.0);
rotation.m34 = 0.0059;
cell.layer.anchorPoint = CGPointMake(0.5, 0.0);
}
else
{
rotation = CATransform3DMakeRotation((90.0*M_PI)/180, 1.5, 0.0, 0.0);
rotation.m34 = 0.0059;
cell.layer.anchorPoint = CGPointMake(0.5, 1.0);
}
cell.layer.transform = CATransform3DConcat(position, rotation);
//3. Define the final state (After the animation) and commit the animation
[UIView beginAnimations:#"rotation" context:NULL];
[UIView setAnimationDuration:2.3];
//[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:cell cache:YES];
cell.layer.transform = CATransform3DIdentity;
cell.alpha = 1;
shadow.alpha = 0;
//cell.layer.shadowOff
1. List item
set = CGSizeMake(0, 0);
[UIView commitAnimations];
}
}
Now I don't have any issue in particular with the animation. The cells are rotating just fine. However my issue is that when a cell i inserted even while rotated it is still taking up space as if it were fully open.
As you can see the cells are rotating within their "virtual boxes" however I want the cells to stick together as depicted in this image:
Does anyone have any idea on how to do this?
You are not transforming the frame, only the layer. The layer is only the visual representation of the cell, and does not define size or position of the cell.
I believe, in a tableView, the frame of a cell is defined first by the heightForRowAtIndexPath: method, but you should be able to change and animate it later using the "frame" property. Try setting it to zero, and animate to the final height when the cell appears.
Code, that finally gave the solution: https://www.cocoacontrols.com/controls/jtgesturebasedtableviewdemo
Here is some code, that actually works (tested it).
Put it into the
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath method, and comment out the other code for testing
CGRect cellFrame = cell.frame;
//replace with the y coordinate of the point, where the cell(s) get inserted
float insertPoint = 0;
//replace with the duration you wish
float animationDuration = 5;
[cell setFrame:CGRectMake(cellFrame.origin.x, insertPoint, cellFrame.size.width, 0)];
[UIView animateWithDuration:animationDuration animations:^{
[cell setFrame:cellFrame];
}];
That may not be exactly what you want, but it should give you an idea...

Resources