Is there a way to detect a running animation in iOS? - ios

I'm trying to find a way to detect if a view is being animated.
Case in point: I have applied a shadow on the layer of a view, specifying a shadowPath for performance. When the view is resized, the shadow should animate along. I can observe the frame of the view, and change the shadowPath of the layer accordingly. But while the view is resizing, the shadow jumps ahead since the change is not animated.
I know how to animate the shadowPath using a CABasicAnimation, but I need to know the properties of an ongoing animation so that I can apply them to my animation as well (mostly: duration, easing).
This is in a framework-type component, so I just cannot assume I know the duration and easing properties on beforehand.
Is there a way to detect a starting/running animation when observing the frame?

You can retrieve all animations attached to particular view's layer knowing it's key by calling
[yourView.layer animationForKey:#"key"]
to get all keys there is some animation for, call
NSArray* keys = [yourView.layer animationKeys];

I think the best practice should be....
UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.7];
[UIView setAnimationBeginsFromCurrentState:YES];
.....your code
// Set animation did stop selector before committing the animations
[UIView setAnimationDidStopSelector:#selector(animationFinished:)];
[UIView commitAnimations];

Related

Why are animations triggered by calling .animate() on TYPE UIView

Here's something I don't find to be logical:
When animating the property of a UIView (in this case mainView) the funciton is called on the generic type, instead of on the specific Instance of UIView
UIView.animate(withDuration: flashDuration, delay: 0, options: UIViewAnimationOptions.allowUserInteraction, animations: {
self.mainView.backgroundColor = self.black
}, completion: nil)
This is quite useful, because I can now animate different instances of UIView in the same place, but how is it possible to call a function on a generic type?
I have never came across this anywhere else in Swift.
I don't know what exactly happens under the hood because UIKit isn't open-source
Before the block-based UIView animation methods were introduced, animating views looked like this, and those methods are actually still available:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
myView.center = CGPointMake(300, 300);
[UIView commitAnimations];
Knowing this, we could implement our own block-based animation method like this:
(void)my_animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
animations();
[UIView commitAnimations];
}
The actual animation is performed by Core Animation, which works at the layer level – each UIView has a backing CALayer instance that is responsible for animations and compositing, while the view mostly just handles touch events and coordinate system conversions.
I won't go into detail here on how Core Animation works, you might want to read the Core Animation Programming Guide for that. Essentially, it's a system to animate changes in a layer tree, without explicitly calculating every keyframe (and it's actually fairly difficult to get intermediate values out of Core Animation, you usually just specify from and to values, durations, etc. and let the system take care of the details).

How does the UIView.animate function internally work? [duplicate]

I'm new to Objective-C/iOS programming and I'm trying to understand how UIView animation works under the hood.
Say I have a code like this:
[UIView animateWithDuration:2.0 animations:^{
self.label.alpha = 1.0;
}];
The thing that gets passed as an animations argument is an Objective-C block (something like lambdas/anonymous functions in other languages) that can be executed and then it changes the alpha property of label from current value to 1.0.
However, the block does not accept an animation progress argument (say going from 0.0 to 1.0 or from 0 to 1000). My question is how the animation framework uses this block to know about intermediate frames, as the block only specifies the final state.
EDIT:
My questions is rather about under the hood operation of animateWithDuration method rather than the ways to use it.
My hypothesis of how animateWithDuration code works is as follows:
animateWithDuration activates some kind of special state for all view objects in which changes are not actually performed but only registered.
it executes the block and the changes are registered.
it queries the views objects for changed state and gets back the initial and target values and hence knows what properties to change and in what range to perform the changes.
it calculates the intermediate frames, based on the duration and initial/target values, and fires the animation.
Can somebody shed some light on whether animateWithDuration really works in such way?
Of course I don't know what exactly happens under the hood because UIKit isn't open-source and I don't work at Apple, but here are some ideas:
Before the block-based UIView animation methods were introduced, animating views looked like this, and those methods are actually still available:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
myView.center = CGPointMake(300, 300);
[UIView commitAnimations];
Knowing this, we could implement our own block-based animation method like this:
+ (void)my_animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
animations();
[UIView commitAnimations];
}
...which would do exactly the same as the existing animateWithDuration:animations: method.
Taking the block out of the equation, it becomes clear that there has to be some sort of global animation state that UIView then uses to animate changes to its (animatable) properties when they're done within an animation block. This has to be some sort of stack, because you can have nested animation blocks.
The actual animation is performed by Core Animation, which works at the layer level – each UIView has a backing CALayer instance that is responsible for animations and compositing, while the view mostly just handles touch events and coordinate system conversions.
I won't go into detail here on how Core Animation works, you might want to read the Core Animation Programming Guide for that. Essentially, it's a system to animate changes in a layer tree, without explicitly calculating every keyframe (and it's actually fairly difficult to get intermediate values out of Core Animation, you usually just specify from and to values, durations, etc. and let the system take care of the details).
Because UIView is based on a CALayer, many of its properties are actually implemented in the underlying layer. For example, when you set or get view.center, that is the same as view.layer.location and changing either of these will also change the other.
Layers can be explicitly animated with CAAnimation (which is an abstract class that has a number of concrete implementations, like CABasicAnimation for simple things and CAKeyframeAnimation for more complex stuff).
So what might a UIView property setter do to accomplish "magically" animating changes within an animation block? Let's see if we can re-implement one of them, for simplicity's sake, let's use setCenter:.
First, here's a modified version of the my_animateWithDuration:animations: method from above that uses the global CATransaction, so that we can find out in our setCenter: method how long the animation is supposed to take:
- (void)my_animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
{
[CATransaction begin];
[CATransaction setAnimationDuration:duration];
animations();
[CATransaction commit];
}
Note that we don't use beginAnimations:... and commitAnimations anymore, so without doing anything else, nothing will be animated.
Now, let's override setCenter: in a UIView subclass:
#interface MyView : UIView
#end
#implementation MyView
- (void)setCenter:(CGPoint)position
{
if ([CATransaction animationDuration] > 0) {
CALayer *layer = self.layer;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [layer valueForKey:#"position"];
animation.toValue = [NSValue valueWithCGPoint:position];
layer.position = position;
[layer addAnimation:animation forKey:#"position"];
}
}
#end
Here, we set up an explicit animation using Core Animation that animates the underlying layer's location property. The animation's duration will automatically be taken from the CATransaction. Let's try it out:
MyView *myView = [[MyView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
myView.backgroundColor = [UIColor redColor];
[self.view addSubview:myView];
[self my_animateWithDuration:4.0 animations:^{
NSLog(#"center before: %#", NSStringFromCGPoint(myView.center));
myView.center = CGPointMake(300, 300);
NSLog(#"center after : %#", NSStringFromCGPoint(myView.center));
}];
I'm not saying that this is exactly how the UIView animation system works, it's just to show how it could work in principle.
The values intermediate frames for are not specified; the animation of the values (alpha in this case, but also colours, position, etc) is generated automatically between the previously set value and the destination value set inside the animation block. You can affect the curve by specifying the options using animateWithDuration:delay:options:animations:completion: (the default is UIViewAnimationOptionCurveEaseInOut, i.e., the speed of the value change will accelerate and decelerate).
Note that any previously set animated changes of values will finish first, i.e., each animation block specifies a new animation from the previous end value to the new. You can specify UIViewAnimationOptionBeginFromCurrentState to start the new animation from the state of the already in-progress animation.
This will change the property specified inside the block from the current value to whatever value you provide, and will do it linearly over the duration.
So if the original alpha value was 0, this would fade in the label over 2 seconds. If the original values was already 1.0, you wouldn't see any effect at all.
Under the hood, UIView takes care of figuring out over how many animation frames the change needs to take place.
You can also change the rate at which the change takes place by specifying an easing curve as a UIViewAnimationOption. Again, UIView handles the tweening for you.

Objective c UIImageView animation

I am trying to take a UIImageView and hide it gradually from bottom to top.
What is the best and most efficient way to do it in Objective-C?
I am looking into CABasicAnimation Class and Animation Class.
If you want know CoreAnimation deeply, I think you can use this:
CABasicAnimation *animationA=[CABasicAnimation animation];
animationA.keyPath=#"position.y";
animationA.fromValue=#0;
animationA.toValue=#250;
animationA.duration=3;
animationA.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[yourTestView.layer addAnimation:animationA forKey:#"basic"];
yourTestView.layer.position=CGPointMake(250, 50);
or if you want to only support IOS7, you can try learn UIAttachmentBehavior and UIDynamicItemBehavior to achieve more interactive animation.
All animations like this use Core Animation and can be done really simply using the block based animations.
For this you would do something like...
[UIView animateWithDuration:3
animations:^{
imageView.frame = CGRectMake(//the end frame of the image view
}];
This will then animate the change over the duration given. (3 seconds in this case).
There are other versions that you can find in the docs of UIView that give you more options like changing the animation curve or running some code on completion etc...
NOTE
This assumes you are not using Auto Layout. If you are using auto layout then the method is exactly the same but you need to change the constraints and then run [view layoutIfNeeded]; in the animations block.

Check if UIView is currently animating

I have two animations running; show search bar and show banner. Both those animations are resizing the same view and if they are running on the same time (Which they are) the latest animation will cancel the resize. Is there anyway to check if UIView is currently animating and then standby for animation?
I'm pretty sure I'm not using CAAnimations, since Cocoa not is detecting such class.
This one is running when an ad was received. Unfortunately, that is the same time as ShowSearch is running.
- (void)adViewDidReceiveAd:(GADBannerView *)bannerView {
if (!hasloaded) {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration: 1.0];
[bannerView_ setFrame:CGRectMake(0, self.view.frame.size.height, bannerView_.frame.size.width, bannerView_.frame.size.height)];
// move and grow
[bannerView_ setFrame:CGRectMake(0, self.view.frame.size.height-50, bannerView_.frame.size.width, bannerView_.frame.size.height)];
// set original position
[UIT setFrame:CGRectMake(UIT.frame.origin.x, UIT.frame.origin.y, UIT.frame.size.width, UIT.frame.size.height)];
// move and grow
[UIT setFrame:CGRectMake(UIT.frame.origin.x, UIT.frame.origin.y, UIT.frame.size.width, UIT.frame.size.height-50)];
[UIView commitAnimations];
hasloaded = true;
}
}
You can use the completion block in the UIView method +animateWithDuration:animations:completion: (which is a more modern alternative to beginAnimations/commitAnimations) to chain multiple animations (I'm guessing this is what you want to do?).
If you select your code while entering it and press control + K, you will preserve formatting and make it pretty. Try it. Reading the wall of text made from pasting code into a true-type non-color-formatted environment.
Nick Weaver says:
A UIView has a layer (CALayer). You can send animationKeys to it which will give you an array of keys which identify the animations attached to the layer. I suppose that if there are any entries, the animation(s) are running. If you want to dig even deeper have a look at the CAMediaTiming protocol which CALayer adopts. It does some more information on the current animation.

ios MapKit MKOverlayView animation happening instantly

I am trying to animate the alpha value of a MapKit overlay view (specifically an MKCircleView) in iOS 5 using the following code:
-(void) animateCircle:(MKCircle*)circle onMap:(MKMapView*) mapView
{
MKCircleView * circleView = (MKCircleView*) [mapView viewForOverlay:circle];
UIViewAnimationOptions options = UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionTransitionNone;
[UIView animateWithDuration:5.0
delay:0.0
options:options
animations:^(void) { circleView.alpha = 0.9; }
completion:^(BOOL finished) {}
];
}
The alpha value of the overlay is changing as I want, but it is jumping there instantaneously rather than animating over the specified duration.
Can anyone suggest what might be wrong? Perhaps animation on overlay views os more complex with blocks than I had thought.
Core Animation has interesting behavior when concurrent animations effect the same view... If you try to animate a view before the view's last animation finished, it will assume you intended the subsequent animation to start from the desired end-state of the initial one. This can result in jumps of frames as well as jumps of alpha values.
In your case, this view is likely being animated by something else. Try locating and removing the other animation / or'ing in UIViewAnimationOptionBeginFromCurrentState to its options.

Resources