I am trying to animate a dot around a rect in a circular fashion.
For that I created an oval UIBezierPath, created a CAKeyFrameAnimation and set the path for keyPath 'position'.
I setup a project to experiment with animations and got something working nicely.
The problem is, when I add the code to my app, the dot isn't animating.
I tried to add that same code to other apps and it work just fine. It doesn't work just for that one app.
The strange thing is that if I replace the path by an array of value containing CGPoint then the dot animates. It's just not working for CGPath for that specific app.
Is there a hidden setting somewhere that blocks CAKeyFrameAnimation using CGPath?
Is there another way to do it? Using values it doesn't curve nicely like it does with a path.
Here is the code I used if that helps:
CAKeyframeAnimation *orbit = [CAKeyframeAnimation animation];
orbit.keyPath = #"position";
orbit.path = [self foregroundArcPath].CGPath;
orbit.duration = 0.7f;
orbit.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
orbit.repeatCount = HUGE_VALF;
orbit.calculationMode = kCAAnimationPaced;
[self.foregroundDot addAnimation:orbit forKey:#"orbit"];
Like I said, this code works, just not in this one app.
It's my first post here, sorry if it's poorly formulated.
Perhaps foregroundArcPath is nil or an empty path?
I apologize for giving an answer with little detail. I would have just commented but I am not yet able to do that.
I encounter exactly problem as you did. Perhaps your project import some NSObject override class, such as RMMapper.
-(instancetype)copyWithZone:(NSZone *)zone {
typeof(self) copiedObj = [[[self class] allocWithZone:zone] init];
CGPath is not suitable for the copy zone.
Hoping this tip will help you.
Related
I am running into an issue when I create an explicit animation to change the value of a CAShapeLayer's path from an ellipse to a rect.
In my canvas controller I setup a basic CAShapeLayer and add it to the root view's layer:
CAShapeLayer *aLayer;
aLayer = [CAShapeLayer layer];
aLayer.frame = CGRectMake(100, 100, 100, 100);
aLayer.path = CGPathCreateWithEllipseInRect(aLayer.frame, nil);
aLayer.lineWidth = 10.0f;
aLayer.strokeColor = [UIColor blackColor].CGColor;
aLayer.fillColor = [UIColor clearColor].CGColor;
[self.view.layer addSublayer:aLayer];
Then, when I animate the path I get a strange glitch / flicker in the last few frames of the animation when the shape becomes a rect, and in the first few frames when it animates away from being a rect. The animation is set up as follows:
CGPathRef newPath = CGPathCreateWithRect(aLayer.frame, nil);
[CATransaction lock];
[CATransaction begin];
[CATransaction setAnimationDuration:5.0f];
CABasicAnimation *ba = [CABasicAnimation animationWithKeyPath:#"path"];
ba.autoreverses = YES;
ba.fillMode = kCAFillModeForwards;
ba.repeatCount = HUGE_VALF;
ba.fromValue = (id)aLayer.path;
ba.toValue = (__bridge id)newPath;
[aLayer addAnimation:ba forKey:#"animatePath"];
[CATransaction commit];
[CATransaction unlock];
I have tried many different things like locking / unlocking the CATransaction, playing with various fill modes, etc...
Here's an image of the glitch:
http://www.postfl.com/outgoing/renderingglitch.png
A video of what I am experiencing can be found here:
http://vimeo.com/37720876
I received this feedback from the quartz-dev list:
David Duncan wrote:
Animating the path of a shape layer is only guaranteed to work when
you are animating from like to like. A rectangle is a sequence of
lines, while an ellipse is a sequence of arcs (you can see the
sequence generated by using CGPathApply), and as such the animation
between them isn't guaranteed to look very good, or work well at all.
To do this, you basically have to create an analog of a rectangle by
using the same curves that you would use to create an ellipse, but
with parameters that would cause the rendering to look like a
rectangle. This shouldn't be too difficult (and again, you can use
what you get from CGPathApply on the path created with
CGPathAddEllipseInRect as a guide), but will likely require some
tweaking to get right.
Unfortunately this is a limitation of the otherwise awesome animatable path property of CAShapeLayers.
Basically it tries to interpolate between the two paths. It hits trouble when the destination path and start path have a different number of control points - and curves and straight edges will have this problem.
You can try to minimise the effect by drawing your ellipse as 4 curves instead of a single ellipse, but it still isn't quite right. I haven't found a way to go smoothly from curves to polygons.
You may be able to get most of the way there, then transfer to a fade animation for the last part - this won't look as nice, though.
There are many situations where you need to "shake" a UIView.
(For example, "draw child user's attention to a control", "connection is slow", "user enters bad input," and so on.)
Would it be possible to do this using UIKit Dynamics?
So you'd have to ..
take the view, say at 0,0
add a spring concept
give it a nudge, say to the "left"
it should swing back and fore on the spring, ultimately settling again to 0,0
Is this possible? I couldn't find an example in the Apple demos. Cheers
Please note that as Niels astutely explains below, a spring is not necessarily the "physics feel" you want for some of these situations: in other situations it may be perfect. As far as I know, all physics in iOS's own apps (eg Messages etc) now uses UIKit Dynamics, so for me it's worth having a handle on "UIView bouncing on a spring".
Just to be clear, of course you can do something "similar", just with an animation. Example...
But that simply doesn't have the same "physics feel" as the rest of iOS, now.
-(void)userInputErrorShake
{
[CATransaction begin];
CAKeyframeAnimation * anim =
[CAKeyframeAnimation animationWithKeyPath:#"transform"];
anim.values = #[
[NSValue valueWithCATransform3D:
CATransform3DMakeTranslation(-4.0f, 0.0f, 0.0f) ],
[NSValue valueWithCATransform3D:
CATransform3DMakeTranslation(4.0f, 0.0f, 0.0f) ]
];
anim.autoreverses = YES;
anim.repeatCount = 1.0f;
anim.duration = 0.1f;
[CATransaction setCompletionBlock:^{}];
[self.layer addAnimation:anim forKey:nil];
[CATransaction commit];
}
If you want to use UIKit Dynamics, you can:
First, define a governing animator:
#property (nonatomic, strong) UIDynamicAnimator *animator;
And instantiate it:
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
Second, add attachment behavior to that animator for view in current location. This will make it spring back when the push is done. You'll have to play around with damping and frequency values.
UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:viewToShake attachedToAnchor:viewToShake.center];
attachment.damping = 0.5;
attachment.frequency = 5.0;
[self.animator addBehavior:attachment];
These values aren't quite right, but perhaps it's a starting point in your experimentation.
Apply push behavior (UIPushBehaviorModeInstantaneous) to perturb it. The attachment behavior will then result in its springing back.
UIPushBehavior *push = [[UIPushBehavior alloc] initWithItems:#[viewToShake] mode:UIPushBehaviorModeInstantaneous];
push.pushDirection = CGVectorMake(100, 0);
[self.animator addBehavior:push];
Personally, I'm not crazy about this particular animation (the damped curve doesn't feel quite right to me). I'd be inclined use block based animation to move it one direction (with UIViewAnimationOptionCurveEaseOut), upon completion initiate another to move it in the opposite direction (with UIViewAnimationOptionCurveEaseInOut), and then upon completion of that, use the usingSpringWithDamping rendition of animateWithDuration to move it back to its original spot. IMHO, that yields a curve that feels more like "shake if wrong" experience.
I have looked at every other answer on here (and across the web) to a situation like mine and have yet to remedy the issue. Essentially what is occurring is I am animating an image-containing CALayer across a quadratic bezier curve. The animation works perfectly, but I really need to implement post-animation behavior, and every attempt I have made at doing so has failed. Here is my code:
UIBezierPath *path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:P(67, 301)];
[path1 addQuadCurveToPoint:P(254, 301) controlPoint:P(160, 93)];
CALayer *ball1 = [CALayer layer];
ball1.bounds = CGRectMake(67, 301, 16.0, 16.0);
ball1.position = P(67, 301);
ball1.contents = (id)([UIImage imageNamed:#"turqouiseBallImage.png"].CGImage);
[self.view.layer addSublayer:ball1];
CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation1.path = path1.CGPath;
animation1.rotationMode = kCAAnimationRotateAuto;
animation1.repeatCount = 2.0;
animation1.duration = 5.0;
[ball1 addAnimation:animation1 forKey:#"ball1"];
[animation1 setDelegate:self];
I'm trying to set the delegate of the animation to self (my primary view controller; this is all in viewDidLoad), whose code implements the animationDidStop method:
(interface) (only showing relevant code)
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag;
(implementation)
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag; {
NSLog(#"animation did stop");
}
I do not see what I am doing that is causing the method not to be called. From my understanding (I am rather new to iOS programming, granted), the CAKeyFrameAnimation animation1 automatically sends the animationDidStop method to its designated delegate when it ceases to animate, which is precisely why I can't identify an issue.
Every other solution or alternative that I have seen has either not worked or suggested using block animation, for which I could not find a way to animate over a bezier curve.
Set the animation's delegate before calling -[CALayer addAnimation:forKey:].
Note the comment in CALayer.h:
The animation is copied before being added to the layer, so any
subsequent modifications to anim will have no affect unless it is
added to another layer.
I want to animate an object along a path. I got it to work, but the movement is not linear. (it slows down a lot along minor bends in the path.)
Here are the main steps.
1) Import the path from an SVG file using PockeSVG
-(CGMutablePathRef)makeMutablePathFromSVG:(NSString *)svgFileName{
PocketSVG *myVectorDrawing0 = [[PocketSVG alloc] initFromSVGFileNamed:svgFileName];
UIBezierPath *myBezierPath0 = myVectorDrawing0.bezier;
return CGPathCreateMutableCopy([myBezierPath0 CGPath]);
}
2) Create the CAKeyFrameAnimation
CAKeyframeAnimation *moveAlongPath = [CAKeyframeAnimation animationWithKeyPath:#"position"];
CGMutablePathRef animationPath = CGPathCreateMutableCopy(pathForwardTo5);
[moveAlongPath setPath:animationPath];
[moveAlongPath setDuration:1.0f];
moveAlongPath.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
moveAlongPath.delegate = self;
[[trackButton layer] addAnimation:moveAlongPath forKey:#"moveButtonAlongPath"];
CFRelease(animationPath);
I tried using other CAMediaTimingFunctions and they all exhibit this kind of behavior.
Question: The animation itself works, but it doesn't follow a smooth and consistent speed. Any idea why?
The slow downs that you are seeing is due to the default calculation mode where each segment of the animation takes the same time. This means that very short segments are going to move slower.
If you look a the documentation for the path property, you will see how to achieve a constant velocity along the path:
How the animation proceeds along the path is dependent on the value in the calculationMode property. To achieve a smooth, constant velocity animation along the path, set the calculationMode property to kCAAnimationPaced or kCAAnimationCubicPaced.
So, to get a constant pace throughout the animation you should set the calculationMode to kCAAnimationPaced.
I've been searching around, but couldn't find an answer that seems to address my specific issue. In my app, I have a custom UIView that animates indefinitely. It's a piece of seaweed, and the animation is very subtle, to make it look like it's swaying in water. I do this with CAKeyframeAnimation objects on the transform.rotation.z and position keys. These are added to a CAAnimationGroup, which is added as a layer to my UIView, like so:
animGroup = [CAAnimationGroup animation];
animGroup.fillMode = kCAFillModeForwards;
[animGroup setAnimations:[NSArray arrayWithObjects:posAnim, rotateAnim, nil]];
animGroup.duration = prpAnim.duration;
animGroup.repeatCount = prpAnim.repeat;
animGroup.delegate = self;
[animGroup setValue:self forKey:[NSString stringWithFormat:#"paper.rot.pos.%d",objID]];
[self.layer addAnimation:animGroup forKey:[NSString stringWithFormat:#"rot.pos.%d",objID]];
I want to further rotate that UIView image (piece of seaweed) when tilting the iPad without disturbing the core animation. I can do this when it's not animated by keyframe, but when I try to combine the two, it doesn't work and I can't figure it out.
I've tried animating the layer using something like this:
CATransform3D rotatePiece3D = CATransform3DMakeRotation(-(tiltRadians), 0.0, 0.0, 1.0);
seaweedPiece.layer.transform = rotatePiece3D;
But that doesn't work either - only when the animation group is turned off. It's a 2D app, so I just want it rotate around the z-axis when tilting left or right. Any ideas how to do this?
You apparently can't alter the rotation separately while it's being animated, so what I was trying to do won't work. I ended up changing the animation key to path, which allowed me to rotate the view separately using the accelerometer.