CATransform3D UIView animateWithDuration sequence - objective-c

I'm a Core Animation beginner trying to animate a view in 3D sequentially. However, I'm running into issues with the 2nd animation. Essentially, once the view rotates and translates to a certain point, I'm making another translation.
When I run it, it seems like it's working, but the second animation translates the view to its destination but then returns to its initial position! It is as if a 3rd phantom animation happens to return the view to its initial state.
Can someone explain why this is happening and how I can fix it?
Thank you!
CATransform3D translation = CATransform3DMakeTranslation(0.0, -50.0, 0.0);
translation.m34 = 1.0 / -500.0f;
CATransform3D finalTransform = CATransform3DRotate(translation, 90.0f, 1.0, 0.0, 0.0);
[UIView animateWithDuration:2.0f animations:^{
[viewImageScroll.layer setTransform:finalTransform];
} completion:^(BOOL finished) {
CATransform3D flyAway = CATransform3DMakeTranslation(0., 0., -10000);
[UIView animateWithDuration:3.0f animations:^{
NSLog(#"done");
[viewImageScroll.layer setTransform:flyAway];
} completion:^(BOOL finished) {
NSLog(#"done and done");
}];
}];

Related

How do i shrink an image horizontally using CGAffineTransform?

I've been using CABasicAnimation to move around images. Now I need to take a view and shrink it horizontally. On the web I see some people using CGAffineTransform to shrink/grow images proportionally. I need the view to shrink horizontally ONLY. See image below:
Any help with this would be greatly appreciated!
CGAffineTransformMakeScale(sx,sy) will do the work.
CGAffineTransform a = CGAffineTransformMakeScale(0.4, 1.0);
editingView.transform = a;
You can use [UIView animateWithDuration:animations:completion:]; if you want it to be animated.
CGAffineTransform a = CGAffineTransformMakeScale(0.4, 1.0);
[UIView animateWithDuration:0.5
animations:^{
editingView.transform = a;
}
completion:^(BOOL finished) {
/* do something after the animation */
}];

Scaling UIView using transform after animating frame change causes UIView to jump back to original frame before scaling

I'm trying to scale a UIView (with animation) after I move it (with animation). The problem is, when the scaling animation begins, it jumps back to the original position. Why?
[UIView animateWithDuration:t delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
// Drop the ball
CGRect frame = coinView.frame;
frame.origin.y += d;
coinView.frame = frame;
coinView.shouldSparkle = NO;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
// Initial scale up for ball "poof"
coinView.transform = CGAffineTransformScale(coinView.transform, 1.5, 1.5);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
coinView.transform = CGAffineTransformScale(coinView.transform, 0.000001, 0.000001);
} completion:^(BOOL finished) {
[coinView removeFromSuperview];
}];
}];
}];
EDIT: This is how I generated my d:
static CGFloat groundPositionY = 325;
CGRect convertedFrame = [coinView.superview convertRect:coinView.frame toView:self.view];
CGFloat d = groundPositionY - CGRectGetMaxY(convertedFrame);
EDIT2: Okay, so I changed the second UIView animation to the following and I discovered that the jump (and the scale down) happens before the second animation occurs, i.e. when animateWithDuration:delay:options:animations:completion: is called.
[UIView animateWithDuration:5 delay:3 options:0 animations:^{
coinView.transform = CGAffineTransformScale(coinView.transform, 1.5, 1.5);
} completion:nil];
I tested your code and it works fine for me. How do you generate your d? and on which block exactly it goes back?
I found the problem...
The coinView is a subview of a child view controller's view. The problem comes from the fact that I overrode that child view controller's viewDidLayoutSubviews method to lay out all the coinView's, so whenever that method was called, the coin view would move back to its original position and size intended by the child view controller.
Thanks for all of your help, repoguy and sergio!!

iOS view transform animation

I'm probably missing something simple, but trying to do a simple "Ken Burns Effect" with an image view.
First the code:
[UIView animateWithDuration:20
delay:2
options:UIViewAnimationCurveLinear
animations:^{
CGAffineTransform move = CGAffineTransformMakeTranslation(40, 40);
CGAffineTransform zoom = CGAffineTransformMakeScale(1.2, 1.2);
CGAffineTransform transform = CGAffineTransformConcat(zoom, move);
self.imageView.transform = transform;
}
completion:^(BOOL finished){
NSLog(#"Done");
}];
I expected this to start with the image view at normal scale and expand it to 120% of the size over 20 seconds. What actually happens is that it starts out immediately smaller than normal size, then expands to normal size.
If I use the reciprocal of the scale value, it starts out zoomed in and then zooms out to normal scale which is the opposite of the effect I want.
Any ideas?
Ok, this actually worked and does what I want.
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
transformAnimation.duration = 20.0;
transformAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
transformAnimation.removedOnCompletion = NO;
transformAnimation.fillMode = kCAFillModeForwards;
CATransform3D xform = CATransform3DIdentity;
xform = CATransform3DScale(xform, 1.2, 1.2, 1.0);
xform = CATransform3DTranslate(xform, 60, -60, 0);
transformAnimation.toValue = [NSValue valueWithCATransform3D:xform];
[self.imageView.layer addAnimation:transformAnimation forKey:#"transformAnimation"];
It sounds like the view is being re-laid out by its parent view in response to the change in transform, casing it to be scaled down to the end result of the transform as soon as the transform is set in the animation block. The key is that your first attempt makes changes directly to the view, while the second approach works with the layer.
Have you try starting form the current transform of your imageView ?
[UIView animateWithDuration:20
delay:2
options:UIViewAnimationCurveLinear
animations:^{
CGAffineTransform trans = self.imageView.transform;
CGAffineTransformTranslate(trans, 40, 40);
CGAffineTransformScale(trans, 1.2, 1.2);
self.imageView.transform = trans;
}
completion:^(BOOL finished){
NSLog(#"Done");
}];

Alpha changing instantly with UIView animateWithDuration: instead of animating

I'm attempting to fade in a UIView that I've created by animating the alpha. I need to fade it in, leave it visible for a few seconds, then fade it out.
The fade out function works fine. The view smoothly disappears. But the fade in just makes the view appear instantly instead of slowly appearing over an interval of 0.5 seconds.
So it seems like the fade in animation isn't working, just instantly setting the alpha to 1.0. I'm kind of at a loss here. Any ideas what I'm doing wrong? Thanks!
-(void)presentPopupPhrase:(NSString *)phrase
inView:(UIView *)view
withDelegate:(id)delegate
andCompletion:(void (^)(BOOL completed))completion {
MessagePopupView *pv = [[[MessagePopupView alloc] initWithFrame:self.frame andText:phrase] autorelease];
pv.alpha = 0.0;
[view addSubview:pv];
[self fadeInMPV:pv
withDuration:self.fadeDuration
andDelay:self.fadeInDelay];
[self fadeOutMPV:pv
withDuration:self.fadeDuration
afterDelay:self.fadeOutDelay
withCompletion:completion
andDelegate:delegate];
}
-(void)fadeInMPV:(MessagePopupView *)mpv
withDuration:(NSTimeInterval)duration
andDelay:(NSTimeInterval)delay
{
[UIView animateWithDuration:duration
delay:delay
options:UIViewAnimationOptionCurveLinear
animations:^{
mpv.alpha = 1.0;
}
completion:nil];
}
-(void)fadeOutMPV:(MessagePopupView *)mpv
withDuration:(NSTimeInterval)duration
afterDelay:(NSTimeInterval)delay
withCompletion:(void (^)(BOOL completed))completion
andDelegate:(id)delegate
{
[UIView animateWithDuration:duration
delay:delay
options:UIViewAnimationOptionCurveLinear
animations:^{
mpv.alpha = 0.0;
}
completion:completion];
}
EDIT:
If it helps, here's the VC code where I'm calling it from:
-(void)viewDidAppear:(BOOL)animated {
CGRect phraseFrame = CGRectMake(20, 341, 280, 65);
PopupPhraseController *phraseController = [[[PopupPhraseController alloc] initWithFrame:phraseFrame] autorelease];
[phraseController presentPopupPhrase:#"Test Phrase" inView:self.view withDelegate:self andCompletion:^(BOOL completed){
if (completed) {
NSLog(#"completed");
} else {
NSLog(#"not completed");
}
NSLog(#"blocked!");
}];
[super viewDidAppear:animated];
}
You can't chain the animations like that as the second animation block essentially causes the first one to be cancelled out.
You have two options for Linking Multiple Animations Together:
Use the completion handler of the first animation
Nest the animations but delay the second one
Looks something like this
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeInDelay
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 1.0;
} completion:^(BOOL finished) {
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeOutDelay
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 0.0;
} completion:completion];
}];
Looks something like this
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeInDelay
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 1.0;
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeOutDelay + self.fadeDuration
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 0.0;
} completion:completion];
} completion:nil];
The issue is because of the way you are setting up the animations.
When you set animatable properties on a view the backing model of the view is updated instantly. So when you set the alpha = 1.0 in the first block the backing data model for the view has the alpha as 1.0 and then the actual animation is kicked off on another thread to show the interpolation between the two values.
When you define the second block straight after the first the view essentially says "ok I need to animate from 1.0 (my current value in the model) to 0.0", which is why you don't see what you expected.
Thanks to Paul.s for dropping the hint there. I think this expected behaviour is a little weird, even if it seems logical from a low level perspective.
To expand on Paul's 2 solutions, there is a 3rd solution which is better IMO.
Use GCD to add a delay to the fade out code, like such:
//Make sure this delay exceeds your fade-in time...
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self fadeOutMPV:pv
withDuration:self.fadeDuration
afterDelay:self.fadeOutDelay
withCompletion:completion
andDelegate:delegate];
});
This solution's better because the executing view controller has complete control on not only when, but what gets to trigger the fade out (for example, a user action).

List examples of how to "briefly draw attention" to an object on screen in iOS?

In iOS, how can one briefly draw attention to an object on screen? Suppose, create a brief glow or cause a shadow to appear and then disappear?
For the purposes of this question, let's define "object on screen" as an instance of UIImageView.
Also, if possible provide an example of how to draw attention to a button.
Most people list code, but I'm sticking to describing some examples;
I've seen objects briefly grow and shrink back to their normal size to draw attention
Bejeweled (a Popcap game) lets diamonds briefly 'shine' (as if sunlight passed over it) to give you a subtle hint
I've seen certain applications use a hand or a fictive character point to a certain object briefly
And of course, you could always introduce a talking paperclip to tell you what's what.
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.1f];
yourView.transform = CGAffineTransformMakeScale(1.1, 1.1);
[UIView commitAnimations];
Or, of course the same thing with a block animations. And after the attention got away from your view you can use :
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.1f];
yourView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
Here is a simple hop animation...
- (void)drawAttn
{
float jumpHeight = 20.0;
CGPoint originalPoint = objectForAttn.center;
CGPoint jumpPoint = CGPointMake(objectForAttn.center.x, objectForAttn.center.y - jumpHeight);
[UIView animateWithDuration:0.20 delay:0.0 options:UIViewAnimationOptionAutoreverse animations:^{
[objectForAttn setCenter:jumpPoint];
} completion:^(BOOL finished) {
/* do other stuff after hop */
}];
}
My app QCount (free in the store, tap a "wrong" number 3 times) uses a fade animation as follows:
(note I wrote this when I was REALLY new to iOS, so there is probably a more compact way to write it)
aLabel = // a UILabel I get from somewhere
aColor = aLabel.backgroundColor;
[UIView animateWithDuration:0.2
delay: 0.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
aLabel.alpha = 0.0;
}
completion:^(BOOL finished){
// Wait .2 seconds and then fade in the view
[UIView animateWithDuration:.2
delay: 0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
aLabel.alpha = 1.0;
}
completion:nil];
}];
There are lots of options: hopping, underlining, vibrating, blinking, rotating, scaling, endarkening around, and their combinations.
But IMO, shine effect on "slide to unlock" text is excellent.
You can check it out here:
iPhone "slide to unlock" animation
I wanted to draw attention to a label whose value had changed. This simple animation produces a nice 'I've changed' animation...
//copy the label
UILabel *newPageLabel = [[UILabel alloc] initWithFrame:_countLabel.frame];
[newPageLabel setFrame:CGRectOffset(newPageLabel.frame, 0, 0)];
newPageLabel.text = _countLabel.text;
newPageLabel.textAlignment = _countLabel.textAlignment;
newPageLabel.backgroundColor = _countLabel.backgroundColor;
newPageLabel.textColor = _countLabel.textColor;
newPageLabel.font = _countLabel.font;
//scale and fade out the copied label
[self.navigationController.toolbar addSubview:newPageLabel];
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionLayoutSubviews
animations:^{
newPageLabel.transform = CGAffineTransformMakeScale(2,2);
newPageLabel.alpha = 0;
}
completion:^(BOOL finished){
[newPageLabel removeFromSuperview];
}
];