UITableView WillDisplayCell Method call after particular time? - ios

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) {
}];
});

Related

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

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.

Animation works on iphone but not on ipad

I'm using this code to animate buttons to bounce with different delays:
- (void) animateWithDuration: (float) duration andObject: (UIButton *) buttonToAnimate{
buttonToAnimate.hidden = YES;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self animateButton: buttonToAnimate];
});
}
- (void) animateButtons
{
[self animateWithDuration:0.3 andObject:self.photoAlbum];
[self animateWithDuration:0.7 andObject:self.camera];
[self animateWithDuration: 0.45 andObject:self.helpInfo];
}
- (void) animateButton: (UIButton *) button
{
button.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.001, 0.001);
button.hidden = NO;
[UIView animateWithDuration:0.3/0.7 animations:^{
button.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3/1.0 animations:^{
button.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.9, 0.9);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3/1.0 animations:^{
button.transform = CGAffineTransformIdentity;
}];
}];
}];
}
It works perfectly on iphone, but on ipad (simulator) the buttons makes a little jump up to the left with about 20 points on the completion block. Then back 20 point on the second completion. Using option: UIViewAnimationOptionTransitionNone (or any of the options) on the animation lessens the jump a tiny bit but you can still see it. Any idea whats wrong?

How do i build 3D transform in iOS for ECSlidingViewController?

I want to build a some special animation based https://github.com/ECSlidingViewController/ECSlidingViewController.
The Zoom animation is like below image.
I just want to rotate the main view controller by PI / 4. Like below image.
I had tried to EndState transform like below code, but it doesn't work.
- (void)topViewAnchorRightEndState:(UIView *)topView anchoredFrame:(CGRect)anchoredFrame {
CATransform3D toViewRotationPerspectiveTrans = CATransform3DIdentity;
toViewRotationPerspectiveTrans.m34 = -0.003;
toViewRotationPerspectiveTrans = CATransform3DRotate(toViewRotationPerspectiveTrans, M_PI_4, 0.0f, -1.0f, 0.0f);
topView.layer.transform = toViewRotationPerspectiveTrans;
topView.layer.position = CGPointMake(anchoredFrame.origin.x + ((topView.layer.bounds.size.width * MEZoomAnimationScaleFactor) / 2), topView.layer.position.y);
}
Any help, pointers or example code snippets would be really appreciated!
I managed to do it. From zoom animation code given in ECSlidingViewController, do not apply zoom factor in
- (CGRect)topViewAnchoredRightFrame:(ECSlidingViewController *)slidingViewController{
CGRect frame = slidingViewController.view.bounds;
frame.origin.x = slidingViewController.anchorRightRevealAmount;
frame.size.width = frame.size.width/* * MEZoomAnimationScaleFactor*/;
frame.size.height = frame.size.height/* * MEZoomAnimationScaleFactor*/;
frame.origin.y = (slidingViewController.view.bounds.size.height - frame.size.height) / 2;
return frame;
}
by commenting MEZoomAnimationScaleFactor.
Then at the top of - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext method add
[topView.layer setAnchorPoint:CGPointMake(0, 0.5)];
[topView.layer setZPosition:100];
Finally the method doing all the transformation must be :
- (void)topViewAnchorRightEndState:(UIView *)topView anchoredFrame:(CGRect)anchoredFrame {
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -0.003;
transform = CATransform3DScale(transform, ARDXZoomAnimationScaleFactor, ARDXZoomAnimationScaleFactor, 1);
transform = CATransform3DRotate(transform, -M_PI_4, 0.0, 1.0, 0.0);
topView.layer.transform = transform;
topView.frame = anchoredFrame;
topView.layer.position = CGPointMake(anchoredFrame.origin.x, anchoredFrame.size.height/2+anchoredFrame.origin.y);
}
Enjoy your animation :)
On top of ryancrunchi's answer, you also need to modify the topViewStartingState to reset the x position to 0 and not the middle of the container frame. This removes the jerky offset at the start of the animations:
Change
- (void)topViewStartingState:(UIView *)topView containerFrame:(CGRect)containerFrame {
topView.layer.transform = CATransform3DIdentity;
topView.layer.position = CGPointMake(containerFrame.size.width / 2, containerFrame.size.height / 2);
}
to
- (void)topViewStartingState:(UIView *)topView containerFrame:(CGRect)containerFrame {
topView.layer.transform = CATransform3DIdentity;
topView.layer.position = CGPointMake(0, containerFrame.size.height / 2);
}
There is also a mistake in the animation that causes a jerky left menu at the end of the opening animation. Copy the following line from the completion part of the animation so that it runs during the animation:
[self topViewAnchorRightEndState:topView anchoredFrame:[transitionContext finalFrameForViewController:topViewController]];
The animation function will now look like:
...
if (self.operation == ECSlidingViewControllerOperationAnchorRight) {
[containerView insertSubview:underLeftViewController.view belowSubview:topView];
[topView.layer setAnchorPoint:CGPointMake(0, 0.5)];
[topView.layer setZPosition:100];
[self topViewStartingState:topView containerFrame:containerView.bounds];
[self underLeftViewStartingState:underLeftViewController.view containerFrame:containerView.bounds];
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration animations:^{
[self underLeftViewEndState:underLeftViewController.view];
underLeftViewController.view.frame = [transitionContext finalFrameForViewController:underLeftViewController];
[self topViewAnchorRightEndState:topView anchoredFrame:[transitionContext finalFrameForViewController:topViewController]];
} completion:^(BOOL finished) {
if ([transitionContext transitionWasCancelled]) {
underLeftViewController.view.frame = [transitionContext finalFrameForViewController:underLeftViewController];
underLeftViewController.view.alpha = 1;
[self topViewStartingState:topView containerFrame:containerView.bounds];
}
[transitionContext completeTransition:finished];
}];
}
...

Slide UILabel Onto View Controller

I'm trying to move a UILabel from a starting position of off the top of my UIViewController in a smooth slide type animation so that it slides down from the top when the view loads and then stops at a y position for about 10 seconds. After 10 seconds I want to slide back off the view again.
-(void) animateInstructionLabel
{
float newX = 50.0f;
float newY = 100.0f;
[UIView transitionWithView:self.lblInstruction
duration:10.0f
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, newY);
}
completion:^(BOOL finished) {
// Do nothing
}];
}
But I don't know how to do the 10 second delay and then move back within the method above. The end result is that I want this to be a label that appears like a notification and then moves off screen again.
Could someone hep me put these pieces described above together?
EDIT
I am adding this based on answer below:
-(void) animateInstructionLabel
{
float newX = lblInstruction.frame.origin.x;
float newY = 20.0f;
lblInstruction.center = CGPointMake(lblInstruction.frame.origin.x, -20.0f);
lblInstruction.bounds = CGRectMake(lblInstruction.frame.origin.x, -20.0f, 650.0f, 40.0f);
[UIView animateWithDuration:3.0f
delay:0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, newY);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:5.0f
delay:5.0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, -20);
}
completion:^(BOOL finished) {
// Do nothing
}
];
}
];
}
But even though I am setting the label.center off the screen before the animation starts I am seeing that it moves from top left corner of the viewcontroller to the center of the viewcontroller. It is not keeping the x position that it should be set to before the animation begins.
Try adding this after your animation block (oldX,oldY are the old coordinates, before animating the label in):
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[UIView transitionWithView:self.lblInstruction
duration:10.0f
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(oldX, oldY);
}
completion:^(BOOL finished) {
// Do nothing
}];
});
Something like this should work:
-(void) animateInstructionLabel {
float newX = 50.0f;
float newY = 100.0f;
labelInstruction.center = CGPointMake(newX, -20); // start off screen
[UIView animateWithDuration:10.0f
delay:0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, newY);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:10.0f
delay:10.0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, -20);
}
completion:^(BOOL finished) {
// Do nothing
}
];
}
];
}
Here is what worked in case it helps somebody else. I had to use .frame instead of .center to set the initial position and then the subsequent animation back to that position.
-(void) animateInstructionLabel
{
lblInstruction.frame = CGRectMake(lblInstruction.frame.origin.x, -40.0f, 650.0f, 40.0f);
[self.view addSubview:lblInstruction];
lblInstruction.hidden = NO;
float newX = lblInstruction.frame.origin.x;
float newY = 20.0f;
[UIView animateWithDuration:3.0f
delay:0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, newY);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:3.0f
delay:5.0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.frame = CGRectMake(lblInstruction.frame.origin.x, -40.0f, 650.0f, 40.0f);
}
completion:^(BOOL finished) {
lblInstruction.hidden = YES;
}
];
}
];
}
This smoothly makes the label slide down from off the view, stop for 5 seconds and then smoothly moves it back vertically off the view.
Thanks for the previous answers which lead me to the above final solution.

Resources