Smooth frame animating on UIView - ios

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).

Related

Migrating from `POPSpringAnimation` to native iOS framework in Swift 4

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.

Updating running animations smoothly

I have a number of images I run continuous animations on. How does one update these running animations smoothly so that there is no visible transition between them, unless part of the animation.
e.g. rotating and scaling an image, and then updating the animation to rotate as it was, but scale up slightly. I currently get a visible change.
I can imagine that that scaling should be done within an animation block and them just run the rotation animation as before, the issue would be then that the rotation would stop while it scales up.
I need it to be seamless. e.g. this code block does not cause a smooth scaling, even though it uses UIViewAnimationOptionBeginFromCurrentState
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseOut //| UIViewAnimationOptionRepeat
animations:(void (^)(void)) ^{
imageViewToScale.transform = CGAffineTransformMakeScale(1.2, 1.2);
}
completion:^(BOOL finished){
//imageViewToScale.transform=CGAffineTransformIdentity;
}];
To be honest if your going to have continuous animations on your views that will alter there states at run time UIView animations isn't he best approach and might cause jitter whilst the completion handler is called over and over e.t.c.
However is you want to do 2 animations in the block you can either set the scale and the rotation in the same block and they will happend simultaneously. Or you could call a function that starts the block and on completion calls it again to see if there are any new animations for that view. When there are none, the completion block just stops, sort of using them recursively, though this isn't the approach i wouldn't recommend if you are doing a lot of animations on a lot of views continuouly.
In OpenGLES you use NSTimer to run continuous animation that would handle the updating of all animations in your app. If you go this route its a lot more hard work and you need to implement the easing/quadratic curve functions for smooth animation yourself. However if you extend the clases you could set states for each image and then when the timer fires you could update its transformation based one the states you have given it. So when it has rotated a certain amount of degrees/radians then you could set it to scale. Now to keep animation smoothly you will need to use NSTimerIntervals to make sure you multiply the distance moved by time elapsed in order to get smooth animation. Personally this is the route i use for things that might be moving on screen constantly but might be over kill if you need to only move things twice and then be done.
EDIT: The code for doing the second step as you asked!
So you need to declare a NSTimer that wil poll your animation steps and an NSTimeInterval so that you update your animation each step only by the amount of time that has passed.
NSTimer *animationTimer;
NSTimeInterval animationInterval;
NSTimeInterval lastUpdateTime;
float currentRotation;
float current scale;
Thirdly you need to set up an NSTimer to fire off updating of your views:
- (void)startAnimation
{
animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:#selector(drawView:) userInfo:nil repeats:YES];
}
- (void)stopAnimation
{
[animationTimer invalidate];
animationTimer = nil;
}
Then you need to kick the thing off when your view starts or when you want to start anmation something like this works
- (void)setAnimationInterval:(NSTimeInterval)interval
{
animationInterval = interval;
if(animationTimer)
{
[self stopAnimation];
[self startAnimation];
}
}
Then In your drawView: Method you need to update your transforms based on time elapsed between each fire of the timer so that the animation is smooth and constant over time. Essentially a linear transform at this point.
- (void)drawView:(id)sender
{
if(lastUpdateTime == -1)
{
lastUpdateTime = [NSDate timeIntervalSinceReferenceDate];
}
NSTimeInterval timeSinceLastUpdate = [NSDate timeIntervalSinceReferenceDate] - lastUpdateTime;
currentRotation = someArbitrarySCALEValue * timeSinceLastUpdate;
currentScale = someArbitraryROTATIONValue * timeSinceLastUpdate;
CGAffineTransform scaleTrans = CGAffineTransformMakeScale(currentScale,currentScale);
CGAffineTransform rotateTrans = CGAffineTransformMakeRotation(currentRotation * M_PI / 180);
imageViewToScale.transform = CGAffineTransformConcat(scaleTrans, rotateTrans);
lastUpdateTime = [NSDate timeIntervalSinceReferenceDate];
}
Note this will not add easing to your animations nor will it at physics to the stopping you will need to play around with that. Also you may need to make sure that when the rotation goes over 360 you reset it to 0 and that you convert between degrees and radians respectively.
Look into using physics for things like bounces, and friction to make things slow nicely.
Look into quadratic graphs for easing to make things move smoothly over time, quadratic interpolation essentially.

How to replace one UI element by another with custom animation?

I want to implement nested commentaries(like stickers) in my own document viewer.
At first, it should be UITextView, but when resignFirstResponder executes, it should become just a small button.
The main question is: how to animate this?
I've read Quartz 2d programming guide from Apple, but it didn't gave me any ideas.
I don't asking for exact or ready solution: keywords, links to articles or documentation are enough.
Thanks for future responses.
You could use this method
[UIView animateWithDuration: delay: options: animations: completion:];
So if you wanted to fade in a button and fade out the textfield it would be
//Starting properties
myButton.alpha = 0;
myTextField.alpha = 1;
//Do the animation
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationCurveEaseInOut animations:^{
myButton.alpha = 1;
myTextField.alpha = 0;
} completion:^(BOOL finished) {
if (finished) {
NSLog(#"finished animating");
}
}];
This will change the opacity of the 2 objects from 0 - 1 / 1 - 0 over 300ms
You can animate many properties this way like size, position, opacity etc.
You could do it the same way that Apple fades between two different elements in QuickLook. You can see the effect yourself in slow-motion by pressing shift+space with an item selected in Finder.
The animation basically is a cross-fade at the same time that the frame changes. You should probably render both the button and the text view into images and do the animation with two image views (that only exist during the animation) to be able to stretch the images as the aspect ratio changes when the frames change.
You will need Core Animation to render the views into images but the rest of the images are only frame and alpha/opacity animations so you should be able to do them with UIView animations (if Core Animation seems to complicated for you). Though Core Animation will give you some more fine grained control when it comes to tweaking the animation.

Unexpected behaviour with UIViewAnimationOptionBeginFromCurrentState

I'm trying to implement multistage animation using UIViewAnimationOptionBeginFromCurrentState to allow the user to cancel the animation at will. The animation is a view that continually and cyclically animates between two sizes. When the user touches the view to cancel the animation, I want the view to quickly revert back to its original, small size, whether it was growing or shrinking at the time.
I'm implementing the multistaging aspect by having two separate animations, one for growing the view and one for shrinking it. Each calls the other routine in its completion block, thus cycling forever unless the abort flag has been set.
I get the expected behaviour if abort is called during the grow-the-view animation: the animation quickly and immediately returns the view to its original, small size and stops. Good!
However, if abort is called during the shrink-the-view animation cycle, the view continues to shrink at the same speed (and then stops as expected), as if the UIViewAnimationOptionBeginFromCurrentState option was never invoked.
Code will hopefully make this clearer and hopefully somebody can see what I can't.
- (void)stopAnimating {
abort = YES;
[UIView animateWithDuration:.2 // some small interval
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{ self.frame = minRect;}
completion:^(BOOL done){}
];
}
- (void)animateSmall {
[UIView animateWithDuration:4
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{self.frame = minRect;}
completion:^(BOOL done){if (!abort)[self animateBig];}
];
}
- (void)animateBig {
[UIView animateWithDuration:4
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{self.frame = maxRect;}
completion:^(BOOL done){if (!abort)[self animateSmall];}
];
}
Just a guess here, because your code looks exactly as I would have done it. But I think what's going on is that the abort animation is setting the same attribute to the same value as the animation it's interrupting, and this gets treated as equivalent and not in need of change (even though the duration changes).
A test of this theory - and a fix to the problem - would be to make your oscillating minRect just a little bit different in size than your steady state minRect.
Hope this works. Good luck.

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