I have an image that I am animating in order to make it look as if it is "breathing".
Currently I have the image moving in a decent manner with the following code below: (I am animating a UIView that contains a few UIImageView's, which all move as one)
- (IBAction)animateButton:(id)sender {
[UIView animateWithDuration:0.64
delay:0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat
animations:^{
_testView.transform = CGAffineTransformMakeScale(1.08f, 1.02f);
} completion:nil];
}
HOWEVER, I can not seem to figure out how to animate stretching the image in the x at a different rate as the y. The point of this is to appear as if the image is actually alive without appearing to cycle through a clear repetitive motion.
I tried by attempting to anchor the center of the UIView to a specific location, then add some number to the width, through an animation of lets say 1.0 seconds.
I then tried to simultaneously call another animation that does the same animation only to the height, with a different amount added, for about 1.3 seconds. I could not get these two to perform at the same time though, as one would take precedence over the other.
If someone could lead me in the right direction as to animating a repetitive stretch of the width and height at different rates I would be most appreciative. Thanks!
Consider that two changes overlapping in time look like this:
|---- change x ---|
|---- change y ----|
If the two intervals are arbitrary and overlapping, the can be represented by three animations: one changing one dimension individually, one changing both dimensions together, and another changing one dimension individually.
You can see that there's numerous ways to specify this, but lets try a straight-forward one. To avoid the arithmetic of compounding scales, lets specify a dimension, a pixel change and a duration. For example...
#[ #{#"dimension":#"width", #"delta":#10, #"duration":0.2},
#{#"dimension":#"both", #"delta":#40, #"duration":0.8},
#{#"dimension":#"width", #"delta":#10, #"duration":0.2} ]
... means a longer change in width straddling a shorter change in height. You can see how this can be a pretty complete language to get done what you want.
We need an animation method that will perform the changes serially. A simple way to do this is to treat the array of actions as a to-do list. The recursive algorithm says: to do a list of things, do the first one, then do the rest....
- (void)animateView:(UIView *)view actions:(NSArray *)actions completion:(void (^)(BOOL))completion {
if (actions.count == 0) return completion(YES);
NSDictionary *action = actions[0];
NSArray *remainingActions = [actions subarrayWithRange:NSMakeRange(1, actions.count-1)];
[self animateView:view action:action completion:^(BOOL finished) {
[self animateView:view actions:remainingActions completion:completion];
}];
}
For the animation, you probably want to use a linear timing curve for the intermediate animations, though I can see you getting more elaborate and change the timing curve at the start and end of the list.
- (void)animateView:(UIView *)view action:(NSDictionary *)action completion:(void (^)(BOOL))completion {
NSString *dimension = action[#"dimension"];
CGFloat delta = [action[#"delta"] floatValue];
NSTimeInterval duration = [action[#"duration"] floatValue];
CGRect frame = view.frame;
if ([dimension isEqualToString:#"width"]) {
frame = CGRectInset(frame, -delta, 0);
} else if ([dimension isEqualToString:#"height"]) {
frame = CGRectInset(frame, 0, -delta);
} else {
frame = CGRectInset(frame, -delta, -delta);
}
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
view.frame = frame;
} completion:completion];
}
If the array of dictionaries is too clumsy to specify (and it is rather general), you could add some convenience methods on top that provide some simpler scheme to the caller and builds the array of more general representation.
Related
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.
I have A class inheriting from UIImageView. Then I have the NSArray containing the the object of the A class containing some pictures. Then I just want to add to the Main view (self.view addSubview: "object of Aclass") and remove some of them. I would keep doing that for a certain amount of time. Here is question. When i do "add" and "remove" the UIImageView (A class object) with some Animation like when I add, the UIImageView goes from small to its original size and when i remove, it goes from its original size to smaller
-(void)displayAClassObject: {
[self.view addSubview: AClassObject];
AClassObject.transform = CGAffineTransformMakeScale(0.3, 0.3);
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ AClassObject.transform = CGAffineTransformIdentity; } completion:^(BOOL finished) { }];
}
Then some function will generate random Number to remove the AClassObject added in the Main view.
-(void)displayOffAClassObject {
[UIView animateWithDuration:0.1 animations:^{AClassObject.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.1, 0.1);} completion:^(BOOL finished){[AClassObject removeFromSuperview]; }];
[AClassObject removeFromSuperview];
}
when i add again the removed AClassObject to the main view, i set it with different frame to place it in different place in the Main view but i do not change its size.
This process keeps going per a second but when I add only or remove only, It works fine. The AClassObject goes from small to its original size and then its original size to small. but when i do both at the same time like [self displayOffAClassObject]; [self displayAClassObject];
The UIImageView (AClassObject) goes really big, even some of the image goes off the screen. Can anyone tell me what is wrong?? or any suggestion to fix it?
When you do transform (especially scale transform), .frame become invalid. Set AClassObject.transform = CGAffineTransformIdentity before setting its frame. If you want to set its position when .transform is not identity, set .center instead.
Cited from UIView documentation:
WARNING
If this property (.transform) is not the identity transform, the value of the frame property is undefined and therefore should be ignored.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/instp/UIView/transform
There are a lot of similar questions but they all differ from this one.
I have UIScrollView which I could both scroll and stop programmatically.
I scroll via the following code:
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{ [self.scrollView scrollRectToVisible:newPageRect animated:NO]; }];
And I don't know how to stop it at all. In all the cases it won't stop or will stop but it also jumps to newPageRect (for example in the case of removeAllAnimations).
Could you suggest how to stop it correctly? Should I possibly change my code for scrolling to another one?
I think this is something you best do yourself. It may take you a few hours to create a proper library to animate data but in the end it can be very rewarding.
A few components are needed:
A time bound animation should include either a CADispalyLink or a NSTimer. Create a public method such as animateWithDuration: which will start the timer, record a current date and set the target date. Insert a floating value as a property which should then be interpolated from 0 to 1 through date. Will most likely look something like that:
- (void)onTimer {
NSDate *currentTime = [NSDate date];
CGFloat interpolation = [currentTime timeIntervalSinceDate:self.startTime]/[self.targetTime timeIntervalSinceDate:self.startTime];
if(interpolation < .0f) { // this could happen if delay is implemented and start time may actually be larger then current
self.currentValue = .0f;
}
else if(interpolation > 1.0f) { // The animation has ended
self.currentValue = 1.0f;
[self.displayLink invalidate]; // stop the animation
// TODO: notify owner that the animation has ended
}
else {
self.currentValue = interpolation;
// TODO: notify owner of change made
}
}
As you can see from the comments you should have 2 more calls in this method which will notify the owner/listener to the changes of the animation. This may be achieved via delegates, blocks, invocations, target-selector pairs...
So at this point you have a floating value interpolating between 0 and 1 which can now be used to interpolate the rect you want to be visible. This is quite an easy method:
- (CGRect)interpolateRect:(CGRect)source to:(CGRect)target withScale:(CGFloat)scale
{
return CGRectMake(source.origin.x + (target.origin.x-source.origin.x)*scale,
source.origin.y + (target.origin.y-source.origin.y)*scale,
source.size.width + (target.size.width-source.size.width)*scale,
source.size.height + (target.size.height-source.size.height)*scale);
}
So now to put it all together it would look something like so:
- (void)animateVisibleRectTo:(CGRect)frame {
CGRect source = self.scrollView.visibleRect;
CGRect target = frame;
[self.animator animateWithDuration:.5 block:^(CGFloat scale, BOOL didFinish) {
CGRect interpolatedFrame = [Interpolator interpolateRect:source to:target withScale:scale];
[self.scrollView scrollRectToVisible:interpolatedFrame animated:NO];
}];
}
This can be a great system that can be used in very many systems when you want to animate something not animatable or simply have a better control over the animation. You may add the stop method which needs to invalidate the timer or display link and notify the owner.
What you need to look out for is not to create a retain cycle. If a class retains the animator object and the animator object retains the listener (the class) you will create a retain cycle.
Also just as a bonus you may very easily implement other properties of the animation such as delay by computing a larger start time. You can create any type of curve such as ease-in, ease-out by using an appropriate function for computing the currentValue for instance self.currentValue = pow(interpolation, 1.4) will be much like ease-in. A power of 1.0/1.4 would be a same version of ease-out.
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 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).