Oscillating UIView With Stopping Motion (iOS) - iphone

I need a view to enter the screen with oscillating animation and finally, the animation should stop in a natural way (decreasing oscillations - pendulum effect). I have added the subview above the screen so that the view rotates into the screen when required. The code for adding the subview is:
myView.layer.anchorPoint = CGPointMake(1.0, 0.0);
[[self view] addSubview:myView];
[myView setHidden:YES];
// Rotate 75 degrees to hide it off screen
CGAffineTransform rotationTransform = CGAffineTransformIdentity;
rotationTransform = CGAffineTransformRotate(rotationTransform, DEGREES_RADIANS(75));
bannerView.transform = rotationTransform;
bannerView.center = CGPointMake(((self.view.bounds.size.width)/2.0), -5.0);
[self performSelector:#selector(animateSwing) withObject:nil afterDelay:3.0];
The way I'm trying to achieve this that the view should rotate one full semi circle & back rotation, then rotate one semi circle rotation and finally come to halt at desired point using EaseOut animation curve. The code for my animateSwing() method is given below:
- (void)animateSwing {
NSLog(#"ANIMATING");
[myView setHidden:NO];
CGAffineTransform swingTransform = CGAffineTransformIdentity;
swingTransform = CGAffineTransformRotate(swingTransform, DEGREES_RADIANS(-20));
[UIView animateWithDuration:0.30
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
[UIView setAnimationRepeatCount:1.5];
[UIView setAnimationRepeatAutoreverses:YES];
myView.transform = swingTransform;
}completion:^(BOOL finished){
[UIView animateWithDuration:0.10
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
myView.transform = CGAffineTransformMakeRotation(DEGREES_RADIANS(0));
}completion:^(BOOL Finished){
}];
}];
}
For some reason the above code isn't working. If I do not chain animations, the code performs the semi-circle routine. But if I chain animations like above, it just oscillates a little bit around the desired point and ends abruptly.
Please suggest a fix to this code OR suggest a way to implement the required animation
Thanks

You want to use a keyframe animation. I actually have an example of a "decreasing waggle" animation in my book (http://www.apeth.com/iOSBook/ch17.html#_keyframe_animation):
CompassLayer* c = (CompassLayer*)self.compass.layer;
NSMutableArray* values = [NSMutableArray array];
[values addObject: #0.0f];
int direction = 1;
for (int i = 20; i < 60; i += 5, direction *= -1) { // alternate directions
[values addObject: #(direction*M_PI/(float)i)];
}
[values addObject: #0.0f];
CAKeyframeAnimation* anim =
[CAKeyframeAnimation animationWithKeyPath:#"transform"];
anim.values = values;
anim.additive = YES;
anim.valueFunction =
[CAValueFunction functionWithName: kCAValueFunctionRotateZ];
[c.arrow addAnimation:anim forKey:nil];
Of course that isn't identical to what you're trying to do, but it should get you started.

Related

Pendulum animation on UIImageView

i face trouble to apply pendulum animation base gravity effect on my UIImageView. can any buddy help me to come out from this trouble??
i have tried below source code. but in that gravitational force is not apply.
- (void)swingPendulum {
pendulum.layer.anchorPoint = CGPointMake(0.5,0);
[pendulum.layer setTransform:CATransform3DMakeRotation(-M_PI*3/4, 0, 0, 1)];
CABasicAnimation *needleAnim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
needleAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
needleAnim.duration = 1;
needleAnim.repeatCount = 5;
needleAnim.autoreverses = YES;
// Setting fillMode means that, once the animation finishes, the needle stays in its end position.
needleAnim.fillMode = kCAFillModeForwards;
needleAnim.removedOnCompletion = YES;
needleAnim.toValue = [NSNumber numberWithFloat:M_PI*3/4];
[pendulum.layer addAnimation:needleAnim forKey:nil];
}
actually we need animation like...
check out this : http://blog.ruben-martins.co.uk/2010/02/maya-tutorials-pendulum-animation.html
Thanks in Advance.
If you've got an image with a blank top section and starts in the middle and just want to rotate it (haven't tried it tho):
-(void) doAnimation {
imgView.transform = CGAffineTransformMakeRotation(-0.7);
[UIView animateWithDuration:4 delay:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAutoreverse animations:^{
imgView.transform = CGAffineTransformMakeRotation(0.7);
} completion:^{
[self doAnimation];
}]

UIView two animations coexisting

How can I have one animation continue forever after calling a second animation? For example:
1) start an object pulsating
2) Move it while its pulsating
3) it continues pulsating
Everything works except the second animation is stopping the first one indefinitely.
Below is some sample code:
//Pulsate **
[UIView animateWithDuration:0.25
delay:0
options: (UIViewAnimationCurveEaseOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat)
animations:^{
CGAffineTransform currentTransform = self.transform;
CGAffineTransform newTransform1 = CGAffineTransformScale(currentTransform, .95, .95);
[self setTransform:newTransform1];
CGAffineTransform newTransform2 = CGAffineTransformScale(currentTransform, 1, 1);
[self setTransform:newTransform2];
}
completion:nil];
//Move **
[UIView animateWithDuration:0.30
delay:0
options: (UIViewAnimationCurveEaseOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState )
animations:^{
[[(UIPinchGestureRecognizer*)sender view] setCenter:CGPointMake(myAppDelegate.MCViewReference.center.x-300, myAppDelegate.MCViewReference.center.y)];
}
completion:^(BOOL finished){
}];
You will not be able to do this with the block-based animations as you have them here. You will need to split your animations up using explicit animations with CABasicAnimation. Create one animation for the pulsating effect and set it to repeat indefinitely. Then you can move it around by setting the center (either animated or non-animated).
CABasicAnimation *pulsation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
pulsation.fromValue = [NSNumber numberWithFloat:0.95f];
pulsation.toValue = [NSNumber numberWithFloat:1.f];
pulsation.duration = 0.25f;
pulsation.autoreverses = YES;
pulsation.repeatCount = INFINITY;
[self.layer addAnimation:pulsation forKey:#"pulse"];
As soon as you add the animation to the layer, it will begin animating. To remove the animation, simply call [self.layer removeAnimationForKey:#"pulse" or removeAllAnimations:.
You could separate your animations into phases and use the completion block to start next phase.

Moving an image along a series of CGPoints

I have path stored in an array of CGPoints which I'd like to move an image along. Here's the general code I have so far:
-(void)movePic:(id)sender{
for(int i = 0; i < self.array.count; i++){
CGPoint location = [[self.array objectAtIndex:i] CGPointValue];
[UIView animateWithDuration:0.1 animations:^{
self.imageView.center = location;
} completion:^(BOOL finished){
}];
}
}
The problem is the for loop runs extremely fast, so you only see the animation on the last points. I'm unsure of how to better design this. Ideally, what could I do to make sure one animation finishes before the other begins? Should I not use a for loop? Thanks
Your code assumes that UIView animations run synchronously in the main thread, which they do not.
You seem to have two options
Explicit CAKeyframeAnimation for animating a CALayer along any amount of sample points (interpolated between them)
Implicit recursive UIView animation for animating a UIView along a series of sample points (interpolated between them)
The former would be much more efficient - still I thought I oould show you both options.
CAKeyframeAnimation
- (void)movePic:(id)sender
{
//create a mutable core-graphics path
CGMutablePathRef path = CGPathCreateMutable();
for(int i = 0; i < self.array.count; i++)
{
CGPoint location = [[self.array objectAtIndex:index] CGPointValue];
CGPathAddLineToPoint(path, nil, location.x, location.y);
}
//create a new keyframe animation
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
//add our path to it
pathAnimation.path = path;
//be nice to the system
CGPathRelease(path);
//setup some more animation parameters
pathAnimation.duration = 0.1 * self.array.count;
//add the animation to our imageView's layer (which will start the animation)
[self.imageView.layer addAnimation:pathAnimation forKey:#"pathAnimation"];
}
UIView Animation
- (void)movePicToPointAtIndex:(unsigned int)index
{
//safeguard check...
if ([self.array count] <= index)
return;
//get the next location
CGPoint location = [[self.array objectAtIndex:index] CGPointValue];
//animate the imageView center towards that location
[UIView animateWithDuration:0.1
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
animations:^{
self.imageView.center = location;
} completion:^(BOOL finished){
//we are done with that animation, now go to the next one...
[self movePicToPointAtIndex:index+1];
}];
}
- (void)movePic:(id)sender
{
[self movePicToPointAtIndex:0];
}
Ok, the thing you have to do is set the array of points as a property of the class, something like animationPath. Ok, so now you would have to pay attention to the delegate methods of the UIView animation delegate methods (it's not actually a different class, it's just a delegate of the class' methods).
Set a method to call on setAnimationDidStopSelector:selector every time the animation stops, so here you would have something like this:
//Inside the callback for setAnimationDidStopSelector
if ([animationPath count] != 0){
//Go to next point
CGPoint location = [[self.array objectAtIndex:0] CGPointValue];
[UIView animateWithDuration:0.1 animations:^{
self.imageView.center = location;
} completion:^(BOOL finished){
}];
}
else{
NSLog(#"Nowhere else to go, animation finished :D");
}
So just be sure to fire your animation with the first point.
As far as I remember UIViews animations manage things in other threads so that's probably why the for statement is not working.

How can I rotate a UIButton continuously and be able to stop it?

I want to rotate a UIButton continuously as a means of indicating that an app is recording something. Is this a good idea. I also want to be able to stop this continuous rotation.
How would I use animation to do this?
Use the UIView animation method:
animateWithDuration:delay:options:animations:completion:
and use the option UIViewAnimationOptionRepeat
For example for a UIButton * outlet named button:
- (void)viewDidLoad
{
[super viewDidLoad];
[UIView animateWithDuration:2.0 delay:0.0 options:UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveLinear animations:^{
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI);
self.button.transform = transform;
} completion:NULL];
}
Since it's going round and round, I just rotate it by pi radians rather than 2pi. you could use any angle you want.
Edit
To stop the animation, just create another animation of a short duration beginning from the current state, e.g.
- (void)stopRotating {
[UIView animateWithDuration:0.1 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveLinear animations:^{
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI * 0.05);
self.button.transform = transform;
} completion:NULL];
}
This provides a very short animation which overrides the current animation. Note that the angle of the transform is multiplied by 0.05 which is the ratio of 0.1/2.0 which means that the rotational speed for this little segment is the same as the continuous rotational speed.
Try below code that will help you because I run that code successfully.
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"Show View" forState:UIControlStateNormal];
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[self.view addSubview:button];
CABasicAnimation *halfTurn;
halfTurn = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
halfTurn.fromValue = [NSNumber numberWithFloat:0];
halfTurn.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
halfTurn.duration = 0.5;
halfTurn.repeatCount = HUGE_VALF;
[[button layer] addAnimation:halfTurn forKey:#"180"];
I had the same problem myself. In the end I made it like that:
#pragma mark Start/StopSpining
-(void)startSpin{
isSpining = YES;
[self Spin];
}
-(void)stopSpin{
isSpining = NO;
}
-(void)spin{
[UIView animateWithDuration:1.0 delay:0 options: UIViewAnimationOptionCurveLinear animations:^{
self.spinCircle.transform = CGAffineTransformRotate(self.spinCircle.transform, M_PI/2);
} completion:^(BOOL finished) {
if (isSpining)
[self spin];
}];
}
Try below extension for swift 3.
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 3) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI * 2)
rotateAnimation.isRemovedOnCompletion = false
rotateAnimation.duration = duration
rotateAnimation.repeatCount=Float.infinity
self.layer.add(rotateAnimation, forKey: nil)
}
}
For start rotation.
MyButton.rotate360Degrees()
And for Stop.
MyButton.layer.removeAllAnimations()
I suggest you don't rotate the UIButton, just the image you put on the button. Who knows what complications or overhead rotating a rectangular object that can detect touches introduces - what happened if your user somehow taps in the corner of the button and then releases the tap as the button has rotated so that the touch is now outside? Does that register a touch up inside or not?
Anyway, if you want continuous rotation use a block method:
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
seems to fit the bill. The complete rotation (2pi) will occur every duration and you cause it to repeat until you stop it using UIViewAnimationOptionRepeat. You can check with completion to see if you should begin another cycle when one animation cycle has ended and you can stop the animation at any time with
[myView.layer removeAllAnimations];
(If you simply want to run it until you stop it then there are other variants of this method without a completion block)
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.3];
uiButtonObject.transform = CGAffineTransformMakeRotation(M_PI);
[UIView commitAnimations];
M_PI is the radian value of the angle you want to rotate. Run this in a loop and in a different thread. You might also want to run the above using performSelectorInMainThread method as UI changes are not permitted in the UIThread.

iPhone SDK: Flip-and-scale animation between view controllers using blocks?

I'm trying to create a flip-and-scale animation between two view controllers. This seems possible using animation blocks available in iOS 4.0, but I'm still unsure how to implement it. I found this SO question which shows a flip animation.
Using this code, flipping between two views works fine, but scaling doesn't -- the flip animation completes and then the new view jumps to the correct size. How would I flip the view and scale it at the same time?
UIView *tempContainer = myView.contentView ;
[UIView transitionWithView:tempContainer
duration:2
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[[[tempContainer subviews] objectAtIndex:0] removeFromSuperview];
[tempContainer addSubview:myOtherViewController.view];
CGAffineTransform transform = CGAffineTransformMakeScale(4.0, 4.0);
tempContainer.transform = transform;
}
completion:^(BOOL finished){
[tempContainer release];
}];
I suppose this happens because the option UIViewAnimationOptionTransitionFlipFromRight somehow overrides everything else. Try using nesting animations or link them together
Here's how I flip and scale between two views of different sizes. I break the animation into a few parts. First I put the back view at the same location as the front view, but make it hidden. I then flip and scale the front view halfway. The back view is given the same transform as the front view, then rotated and scaled the rest of the way. Flipping back is basically the reverse.
I suppose you could use a different view controllers view property as the back view, but I haven't tried that.
// flip and scale frontView to reveal backView to the center of the screen
// uses a containerView to mark the end of the animation
// parameterizing the destination is an exercise for the reader
- (void)flipFromFront:(UIView*)frontView toBack:(UIView*)backView destination:(CGRect)destRect
{
float duration = 0.5;
// distance from center of screen from frontView
float dx = destRect.origin.x + CGRectGetWidth(destRect) / 2 - frontView.center.x;
float dy = destRect.origin.y + CGRectGetHeight(destRect) / 2 - frontView.center.y;
float scale = CGRectGetWidth(destRect) / CGRectGetWidth(frontView.bounds);
// this prevents any tearing
backView.layer.zPosition = 200.0;
// hide the backView and position where frontView is
backView.hidden = NO;
backView.alpha = 0.0;
backView.frame = frontView.frame;
// start the animation
[UIView animateKeyframesWithDuration:duration
delay:0.25
options:UIViewKeyframeAnimationOptionCalculationModeCubic
animations:^{
// part 1. Rotate and scale frontView halfWay.
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.5
animations:^{
// get the transform for the blue layer
CATransform3D xform = frontView.layer.transform;
// translate half way
xform = CATransform3DTranslate(xform, dx/2, dy/2, 0);
// rotate half way
xform = CATransform3DRotate(xform, M_PI_2, 0, 1, 0);
// scale half way
xform = CATransform3DScale(xform, scale/2, scale/2, 1);
// apply the transform
frontView.layer.transform = xform;
}];
// part 2. set the backView transform to frontView so they are in the same
// position.
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.0
animations:^{
backView.layer.transform = frontView.layer.transform;
backView.alpha = 1.0;
}];
// part 3. rotate and scale backView into center of container
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.5
animations:^{
// undo previous transforms with animation
backView.layer.transform = CATransform3DIdentity;
// animate backView into new location
backView.frame = destRect;
}];
} completion:^(BOOL finished) {
self.displayingFront = !self.displayingFront;
}];
}
// flip from back to front
- (void) flipFromBack:(UIView*)backView toFront:(UIView*)frontView
{
float duration = 0.5;
// get distance from center of screen to destination
float dx = backView.center.x - frontView.center.x;
float dy = backView.center.y - frontView.center.y;
backView.layer.zPosition = 200.0;
frontView.hidden = YES;
// this is basically the reverse of the previous animation
[UIView animateKeyframesWithDuration:duration
delay:0
options:UIViewKeyframeAnimationOptionCalculationModeCubic
animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.5
animations:^{
CATransform3D xform = backView.layer.transform;
xform = CATransform3DTranslate(xform, -dx/2, -dy/2, 0);
xform = CATransform3DRotate(xform, M_PI_2, 0, 1, 0);
xform = CATransform3DScale(xform, 0.75, 0.75, 1);
backView.layer.transform = xform;
}];
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.0
animations:^{
backView.alpha = 0.0;
frontView.hidden = NO;
}];
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.5
animations:^{
self.hiddenView.alpha = 0.0;
frontView.layer.transform = CATransform3DIdentity;
}];
} completion:^(BOOL finished) {
self.displayingFront = !self.displayingFront;
}];
}