I am working on an old project and want to get rid of POP framework I am sure that any animation can be done with native iOS framework.
Here is the old code:
POPSpringAnimation *springAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
springAnimation.toValue = [NSValue valueWithCGRect:rect];
springAnimation.velocity = [NSValue valueWithCGRect:CGRectMake(springVelocity, springVelocity, 0, 0)];
springAnimation.springBounciness = springBounciness;
springAnimation.springSpeed = springSpeed;
[springAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) {
if (finished) {
// cool code here
}
}];
[self.selectedViewController.view pop_addAnimation:springAnimation forKey:#"springAnimation"];
What I have tried:
[UIView animateWithDuration:1.0
delay:0
usingSpringWithDamping:springBounciness
initialSpringVelocity:springVelocity
options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.selectedViewController.view.frame = rect;
} completion:^(BOOL finished) {
// cool code here
}];
But I dont get the same result, and some question rises:
is springBounciness in pop equivalent to usingSpringWithDamping ?
What is equivalent of springSpeed in UIView animation ?
what about the duration, what is the duration of POPSpringAnimation ?
Edit:
About the third question I found an issue in Github.
If UIView is not the way to go can that be done using Core Animation or any other iOS native animation framework ?
Pop parameter values range from 0-20. But the usingSpringWithDamping do not have such range. Obviously as Pop is a custom library, it has its own range of values while UIView animation has its own.
From Apple documentation, usingSpringWithDamping parameter is actually damp ratio, and it specifies:
To smoothly decelerate the animation without oscillation, use a value
of 1. Employ a damping ratio closer to zero to increase oscillation.
1.So if you want equivalent bounciness, you need to use values anything below 1, I guess you could try the following formula for springBounciness.
float uiViewBounciness = (20.0 - springBounciness) / 20.0;
.. usingSpringWithDamping:uiViewBounciness ..
2.As for springVelocity, Pop implements a same speed for all animation frames, whereas UIView animation only specifies the INITIAL speed, and this speed is decayed over time based on total duration and damp ratio. So to get as close animation as possible, you could do the following:
float uiViewSpeed = springVelocity * 2.0;
.. initialSpringVelocity:uiViewSpeed ..
3.As for duration, you can implement the same value to animateWithDuration in UIView method.
Finally, you need to experiment with the values and compare it to Pop animations. I don't think you can get the exact same animations with Pop by using UIView animate, but it should be close enough.
Related
I'm trying to find a reason why animation of UIView transform property looks different in iOS 8 than iOS 6/7.
For a simple example, prior to iOS 8:
myView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, 1.57);
[UIView animateWithDuration:5 animations:^{
myView.transform = CGAffineTransformTranslate(plane.transform, 100, 0);
}];
gives expected result, "myView" is rotated 90 degrees and moves down, but in iOS8 when translation is animated it starts at a point that I couldn't find explanation for (which breaks the animation).
Does anyone know the explanation for it? Thanks in advance!
CGAffineTransformIdentity behaves differently on ios7 and ios8. This has to do with auto-layout and size classes. The solution is to remove constraints that conflict with the animation on ios7.
// solve the constraint-animation problem
if(NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1) {
// iOS7 remove constraints that conflict with animation
if (self.centerYAlignment != nil) {
self.view.removeConstraint(self.centerYAlignment) //is an IBOutlet
}
} else {
// iOS8 constraint animations are fine
}
I think the reason is just iOS8 bug, but I use CAAnimation instead, and it works as expected on iOS8.
I had problems with jerky rotation transform in iOS7 as well. Solved this by nesting my rotated view inside a container and centering the rotated view inside.
I'm also experiencing the same issue with scaling. I guess it could be the same with rotation. Could you try this?
myView.transform = CGAffineTransformConcat(myView.transform , CGAffineTransformMakeRotate(1.57));
[UIView animateWithDuration:5 animations:^{
myView.transform = CGAffineTransformTranslate(plane.transform, 100, 0);
}];
Maybe it's also necessary to use CGAffineTransformMakeTranslate and CGAffineTransformConcat that as well, I'm not sure.
The worst part about this is: You would have to do if/else on iOS versions, because this would look weird on iOS 7. I hope this is getting fixed by Apple before or with iOS 8 release.
I agree with Pbk that it has to do with size classes in io8. uiviewcontrollers need to be resized with uitraitcollections depending on the device orientation. Otherwise, you get a uiviewcontroller in portrait mode, while the phone is in landscape mode, when you try to rotate it. So the correct steps are to rotate AND override uitraitcollections
This isn't entirely related, but I was struggling with CGAffineTransformScale not working at all on iOS7 in a fairly complicated animation. It turns out my problem was iOS7 cannot calculate CGAffineTransformScale with CGAffineTransformRotate at the same time. In iOS7, the last animation call you make is the only one that gets animated, so only the rotation was occurring. This bug is fixed in iOS8.
My solution is to simplify my animation for iOS7, only turning on the fancy stuff in iOS8:
//Pre-animation setup:
CGFloat radians = (M_PI/180) * (-15); //Get a human-readable number in degrees
self.badgeImage.alpha = 0; //Start the image as invisible
self.badgeImage.transform = CGAffineTransformScale(self.badgeImage.transform, 1.5, 1.5); //Start the image as scaled bigger than normal
if(NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1) { //See below. We will not be rotating the image in iOS7
self.badgeImage.transform = CGAffineTransformRotate(self.badgeImage.transform, radians); //Rotate the image if iOS8
}
//Animation Pieces:
//Fade in
[UIView animateWithDuration: 0.5
delay:0
options:0
animations:^{
self.badgeImage.alpha = 1.0f; //Return image to opaque
}
completion:NULL];
//Scale with bounce
[UIView animateWithDuration: 1.1
delay:0
usingSpringWithDamping:0.3 //Not as good as Android's bounce interpolator, but I'll take it
initialSpringVelocity:-1.0f //A negative velocity here makes the animation appear more like gravity than spring
options:0
animations:^{
self.badgeImage.transform = CGAffineTransformScale(self.badgeImage.transform, 0.67, 0.67); //Return image to its original size. These arguments are relative to its current scale.
}
completion:NULL];
//Rotation
if(NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1) { //This second animation call negates the first one on iOS7, so remove it.
[UIView animateWithDuration: 0.9
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.badgeImage.transform = CGAffineTransformRotate(self.badgeImage.transform, (radians * -1)); //Rotate the image back to its original orientation if iOS8
}
completion:NULL];
}
Of course, you can still combine multiple effects in iOS7 if you use the confusingly-named CGAffineTransformMakeScale() function. For instance, in the pre-animation setup, you can set both a rotation AND a scale, then set call CGAffineTransformMakeScale(1,1) to reset the image to its original metrics (MakeScale's arguments are specific, not relative - even more confusing!). This isn't always preferable, such as my example above where "bouncing" the animation would also bounce the rotation.
I am looking to have an animation in my iOS app which is almost identical to the animation you see when you use Siri when it is displaying possible questions you can ask it.
The effect is an upward scroll of text replaced by more than come from below but the effect is one in a way where not all the questions move as one together, they seem to be independent and follow each other.
I like this animation and would like to emulate it in my app - but I haven't the first idea how to go around this. My only involvement with animations within my apps so far are ones such as CrossDissolve, FlipFromLeft, etc.
Does anyone know of a tutorial which teaches an effect like this, or can point me in the right direction to start?
Thanks to all in advance.
Having a quick look it looks fairly straight forward.
The animation can be broken down into several stages.
First, there are 5 UILabels animated separately. Each with a very short delay on the previous label.
Each label is animated like this...
Set the frame of the label to be low down on the screen and set alpha to 0.0.
Animate the frame of the label to around 300 points higher on the screen and alpha of 1.0 over a duration of around 0.2 seconds.
Animate the frame of the label to about 30 points higher on the screen over about 2 or 3 seconds.
Animate the frame of the label another 300 points higher on the screen and alpha back to 0.0 over about 0.2 seconds again.
Change text on the label and repeat.
I think the easiest way to do this would be with the method...
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
options:(UIViewKeyframeAnimationOptions)options
animations:(void (^)(void))animations
completion:(void (^)(BOOL finished))completion
Lemme give this a try in actual code and get back to you. Code completion helps a lot.
OK, here goes
Darn, I just installed Yosemite and don't have Xcode. OK, I'll give it a try here...
- (void)animateLabels:(NSArray *)labels
{
CGFloat firstLabelStartTop = 600;
[labels enumerateObjectsUsingBlock:^(UILabel *label, NSUInteger idx, BOOL *stop) {
CGFloat delay = 0.1 * idx;
label.frame = CGRectMake(0, firstLabelStartTop + 30 * idx, 320, 21);
label.alpha = 0.0;
[UIView animateKeyFramesWithDuration:2.4
delay:delay
options:0
animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.1
animations:^{
label.alpha = 1.0;
label.frame = CGRectOffset(label.frame, 0, -200);
}];
[UIView addKeyframeWithRelativeStartTime:0.1
relativeDuration:0.9
animations:^{
label.frame = CGRectOffset(label.frame, 0, -30);
}];
[UIView addKeyframeWithRelativeStartTime:0.9
relativeDuration:0.1
animations:^{
label.alpha = 0.0;
label.frame = CGRectOffset(label.frame, 0, -200);
}];
}
completion:nil];
}];
}
This is a first attempt without Xcode to check my code and without being able to run it at all so it might not be perfect but it should give you an idea of where to go from here.
EDIT
From #WilliamGeorges comment it looks like they used a combination of completely separate animations using the method I detailed in my blog.
You would have to do 3 separate animations on each label but its still a similar idea to what I put in this answer.
I have animated a UIView so that it shrinks when the user touches a toggle button and it expands back to its original size when the user touches the button again. So far everything works just fine. The problem is that the animation takes some time - e.g. 3 seconds. During that time I still want the user to be able to interact with the interface. So when the user touches the button again while the animation is still in progress the animation is supposed to stop right where it is and reverse.
In the Apple Q&As I have found a way to pause all animations immediately:
https://developer.apple.com/library/ios/#qa/qa2009/qa1673.html
But I do not see a way to reverse the animation from here (and omit the rest of the initial animation). How do I accomplish this?
- (IBAction)toggleMeter:(id)sender {
if (self.myView.hidden) {
self.myView.hidden = NO;
[UIView animateWithDuration:3 animations:^{
self.myView.transform = expandMatrix;
} completion:nil];
} else {
[UIView animateWithDuration:3 animations:^{
self.myView.transform = shrinkMatrix;
} completion:^(BOOL finished) {
self.myView.hidden = YES;
}];
}
}
In addition to the below (in which we grab the current state from the presentation layer, stop the animation, reset the current state from the saved presentation layer, and initiate the new animation), there is a much easier solution.
If doing block-based animations, if you want to stop an animation and launch a new animation in iOS versions prior to 8.0, you can simply use the UIViewAnimationOptionBeginFromCurrentState option. (Effective in iOS 8, the default behavior is to not only start from the current state, but to do so in a manner that reflects both the current location as well as the current velocity, rendering it largely unnecessary to worry about this issue at all. See WWDC 2014 video Building Interruptible and Responsive Interactions for more information.)
[UIView animateWithDuration:3.0
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
animations:^{
// specify the new `frame`, `transform`, etc. here
}
completion:NULL];
You can achieve this by stopping the current animation and starting the new animation from where the current one left off. You can do this with Quartz 2D:
Add QuartzCore.framework to your project if you haven't already. (In contemporary versions of Xcode, it is often unnecessary to explicitly do this as it is automatically linked to the project.)
Import the necessary header if you haven't already (again, not needed in contemporary versions of Xcode):
#import <QuartzCore/QuartzCore.h>
Have your code stop the existing animation:
[self.subview.layer removeAllAnimations];
Get a reference to the current presentation layer (i.e. the state of the view as it is precisely at this moment):
CALayer *currentLayer = self.subview.layer.presentationLayer;
Reset the transform (or frame or whatever) according to the current value in the presentationLayer:
self.subview.layer.transform = currentLayer.transform;
Now animate from that transform (or frame or whatever) to the new value:
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
Putting that all together, here is a routine that toggles my transform scale from 2.0x to identify and back:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CALayer *currentLayer = self.subview.layer.presentationLayer;
[self.subview.layer removeAllAnimations];
self.subview.layer.transform = currentLayer.transform;
CATransform3D newTransform;
self.large = !self.large;
if (self.large)
newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
else
newTransform = CATransform3DIdentity;
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
}
Or if you wanted to toggle frame sizes from 100x100 to 200x200 and back:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CALayer *currentLayer = self.subview.layer.presentationLayer;
[self.subview.layer removeAllAnimations];
CGRect newFrame = currentLayer.frame;
self.subview.frame = currentLayer.frame;
self.large = !self.large;
if (self.large)
newFrame.size = CGSizeMake(200.0, 200.0);
else
newFrame.size = CGSizeMake(100.0, 100.0);
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.frame = newFrame;
}
completion:NULL];
}
By the way, while it generally doesn't really matter for really quick animations, for slow animations like yours, you might want to set the duration of the reversing animation to be the same as how far you've progressed in your current animation (e.g., if you're 0.5 seconds into a 3.0 second animation, when you reverse, you probably don't want to take 3.0 seconds to reverse that small portion of the animation that you have done so far, but rather just 0.5 seconds). Thus, that might look like:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CFTimeInterval duration = kAnimationDuration; // default the duration to some constant
CFTimeInterval currentMediaTime = CACurrentMediaTime(); // get the current media time
static CFTimeInterval lastAnimationStart = 0.0; // media time of last animation (zero the first time)
// if we previously animated, then calculate how far along in the previous animation we were
// and we'll use that for the duration of the reversing animation; if larger than
// kAnimationDuration that means the prior animation was done, so we'll just use
// kAnimationDuration for the length of this animation
if (lastAnimationStart)
duration = MIN(kAnimationDuration, (currentMediaTime - lastAnimationStart));
// save our media time for future reference (i.e. future invocations of this routine)
lastAnimationStart = currentMediaTime;
// if you want the animations to stay relative the same speed if reversing an ongoing
// reversal, you can backdate the lastAnimationStart to what the lastAnimationStart
// would have been if it was a full animation; if you don't do this, if you repeatedly
// reverse a reversal that is still in progress, they'll incrementally speed up.
if (duration < kAnimationDuration)
lastAnimationStart -= (kAnimationDuration - duration);
// grab the state of the layer as it is right now
CALayer *currentLayer = self.subview.layer.presentationLayer;
// cancel any animations in progress
[self.subview.layer removeAllAnimations];
// set the transform to be as it is now, possibly in the middle of an animation
self.subview.layer.transform = currentLayer.transform;
// toggle our flag as to whether we're looking at large view or not
self.large = !self.large;
// set the transform based upon the state of the `large` boolean
CATransform3D newTransform;
if (self.large)
newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
else
newTransform = CATransform3DIdentity;
// now animate to our new setting
[UIView animateWithDuration:duration
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
}
There is a common trick you can use to do this, but it is necessary to write a separate method to shrink (and another similar one to expand):
- (void) shrink {
[UIView animateWithDuration:0.3
animations:^{
self.myView.transform = shrinkALittleBitMatrix;
}
completion:^(BOOL finished){
if (continueShrinking && size>0) {
size=size-1;
[self shrink];
}
}];
}
So now, the trick is to break the 3 seconds animation of shrinking into 10 animations (or more than 10, of course) of 0.3 sec each in which you shrink 1/10th of the whole animation: shrinkALittleBitMatrix. After each animation is finished you call the same method only when the bool ivar continueShrinking is true and when the int ivar size is positive (the view in full size would be size=10 and the view with minimum size would be size=0). When you press the button you change the ivar continueShrinking to FALSE, and then call expand. This will stop the animation in less than 0.3 seconds.
Well, you have to fill the details but I hope it helps.
First: how to remove or cancel a animation with view?
[view.layer removeAllAnimations]
if the view have many animations, such as, one animation is move from top to bottom, other is move from left to right;
you can cancel or remove a special animation like this:
[view.layer removeAnimationForKey:#"someKey"];
// the key is you assign when you create a animation
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"someKey"];
when you do that, animation will stop, it will invoke it's delegate:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
if flag == 1, indicate animation is completed.
if flag == 0, indicate animation is not completed, it maybe cancelled、removed.
Second: so , you can do what you want to do in this delegate method.
if you want get the view's frame when the remove code excute, you can do this:
currentFrame = view.layer.presentationlayer.frame;
Note:
when you get the current frame and remove animation , the view will also animate a period time, so currentFrame is not the last frame in the device screen.
I cann't resolve this question at now. if some day I can, I will update this question.
I am trying to move a UIView around the screen by incrementing the UIView's x property in an animation block. I want the element to move continuously so I cannot just specify an ending x and up the duration.
This code works but it is very choppy. Looks great in the simulator but choppy on the device.
-(void)moveGreyDocumentRight:(UIImageView*)greyFolderView
{
[UIView animateWithDuration:0.05 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
NSInteger newX = greyFolderView.frame.origin.x + 5.0;
greyFolderView.frame = CGRectMake(newX, greyFolderView.frame.origin.y, greyFolderView.frame.size.width, greyFolderView.frame.size.height);
}
} completion:^(BOOL finished) {
[self moveGreyDocumentRight:greyFolderView];
}];
}
You're fighting the view animation here. Each one of your animations includes a UIViewAnimationOptionCurveEaseInOut timing curve. That means that every 0.05 seconds you try to ramp up your speed then slow down your speed then change to somewhere else.
The first and simplest solution is likely to change to a linear timing by passing the option UIViewAnimationOptionCurveLinear.
That said, making a new animation every 5ms really fights the point of Core Animation, complicating the code and hurting performance. Send the frame it to the place you currently want it to go. Whenever you want it to go somewhere else (even if it's still animating), send it to the new place passing the option UIViewAnimationOptionBeginFromCurrentState. It will automatically adjust to the new target. If you want it to repeat the animation or bounce back and forth, use the repeating options (UIViewAnimationOptionRepeat and UIViewAnimationOptionAutoreverse).
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.