Is this a good way to use position to move a CALayer permanently? Do I really need a to and from value animation?
serveBlock2 = [CALayer layer];
serveBlock2.zPosition = 1;
[serveBlock2 setFrame:CGRectMake(screenBounds.size.height/2, 0, screenBounds.size.height/2, screenBounds.size.width)];
[serveBlock2 setOpacity:0.0f];
[serveBlock2 setBackgroundColor:[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f].CGColor];
[self.view.layer addSublayer:serveBlock2];
CABasicAnimation *updateCurrentServe2 = [CABasicAnimation animationWithKeyPath:#"position.y"];
serveBlock2.position = CGPointMake((screenBounds.size.height/4)*3, -screenBounds.size.width/2);
[updateCurrentServe2 setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:0.8 :-0.8 :1.0 :1.0]];
[updateCurrentServe2 setDuration:1.5];
[serveBlock2 addAnimation:updateCurrentServe2 forKey:#"serveBlock2 updateCurrentServe2"];
It isn't clear what you are asking.
Core Animation does not actually move the position of the layers it animates. It creates the appearance that the layer is changing (by making changes to the layer's presentation layer) but does not actually change the underlying property. Usually what you do is to start the animation running, then add extra statements to set the property/properties you are changing to their end values. Then when the animation completes the object is in it's final location.
When you create a CAAnimation, you can supply a from and to value or just a to value. If you only provide a to value, it will animate from it's previous position to the new position.
If you just want to move your layer, set it's position property without an animation.
If the layer is the backing layer of a view, it will simply jump to it's new location.
If the layer is some other sublayer, you will get an implicit animation from the old value to the new value.
You have two questions there and I will answer them in the opposite order:
Do I really need a to and from value animation?
No, you don't need that.
CABasicAnimation has three properties that go together define how the interpolation is done, these are fromValue, byValue and toValue. They can be combined in many different ways which is listed under the "Setting Interpolation Values" section in the CABasicAnimation documentation. The very last listed combination is:
All properties are nil. Interpolates between the previous value of keyPath in the target layer’s presentation layer and the current value of keyPath in the target layer’s presentation layer.
So what you are doing and the results you are seeing is documented behavior and works.
Is this a good way to use position to move a CALayer permanently?
This is a very opinionated question and I can only give you my personal opinion and try and explain my reasoning.
I personally don't like it because it is less explicit and you would need to have read this special case in the documentation to know how it works. As you saw in the comment on your question I wasn't even sure how this worked when I first saw it and I consider myself very familiar with Core Animation and have read the documentation many times.
It all should come down to what you and your team thinks is most readable and clear.
Personally I prefer to explicitly set the new model value and then only specify the fromValue, i.e. this case:
fromValue is non-nil. Interpolates between `fromValue and the current presentation value of the property.
Related
I see a lot of code that rotates a view as follows:
CABasicAnimation *centerToRightRotate
= [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
centerToRightRotate.fromValue = #(0);
centerToRightRotate.toValue = #(M_PI/8);
centerToRightRotate.duration = 0.15;
[self.view.layer addAnimation:centerToRightRotate forKey:nil];
(for example, many of the answers to this question)
However, when I try to access self.view.layer.transform.rotation or self.view.layer.transform.rotation.z, the compiler will tell me "No member named 'rotation' in CATransform3D". The docs for CATransform3D also does not show rotation as an instance property.
My guess is CAAnimation is somehow translating the transform.rotation key path into the appropriate transformation, but I want to know what is actually going on under the hood. What exactly is going on here?
According to Key-Value Coding Extensions
Core Animation extends the NSKeyValueCoding protocol as it pertains to the CAAnimation and CALayer classes. This extension adds default values for some keys, expands wrapping conventions, and adds key path support for CGPoint, CGRect, CGSize, and CATransform3D types.
rotation isn't a property of CATransform3D. It's supported key path to specify the field of a data structure that you want to animate with a convenient way.
You can also use these conventions in conjunction with the setValue:forKeyPath: and valueForKeyPath: methods to set and get those fields.
Setting values using key paths is not the same as setting them using Objective-C properties. You cannot use property notation to set transform values. You must use the setValue:forKeyPath: method with the preceding key path strings.
You can use setValue:forKeyPath:, valueForKeyPath: to set or get it but can't use property notation.
If you want to know what is actually going on under the hood, learn more about NSKeyValueCoding here
I'm trying to achieve something similar to the attached image, where the circle is animated depending on your level progress and then a label is attached to both the end of the animated path to show the gained experience and also the undrawn part of the circle to show the remaining experience. I have the circle animating as wanted but am having trouble coming up with a solution to the labels to appear in the right spot.
I've tried setting the position of the label to the path.currentPoint but that always seems to be the start of the drawn path and not the end.
Any pointers on how to achieve this would be great!
I had been working on your question, first of all to achieve this you must animate the path the real path, not only the strokeEnd, if you animate only the strokeEnd your path.currentPoint will always return the circle endPoint of the path, in order to animate the path you need to make a KeyFramed Animation animating the “path” as keyPath and passing an array of paths from current angle to desired one, then in order to set the correct position for your label you need to get de currentPoint of all this paths values and making another keyFramed animation with “position” keyPath and passing as values all this points collected from paths array
This is a basic example working
The code is in GitHub in this Repo
You have a lot of work to do yet but, this can be an starting point for your final solution
Hope this helps, best regards
I have built a circular graph very similar to this example in paintcode:
http://www.paintcodeapp.com/news/animating-apple-watch-activity-rings-in-paintcode
I have successfully drawn out the control on my iOS view with ease, but now I would like to animate the graph so that it begins at 0 and eases towards the specified angle. Basically the animation should look like the first two seconds of the video in the URL above.
What is the best way to go about this type of animation?
FYI: I am working in C#/Xamarin but I am not fussy on syntax at all, so an Objective C or Swift example will do just fine.
I wrote a UIView subclass which does exactly what you're asking for. You just provide an array of rings along with a few other parameters, and it handles all of the setup and management for you.
https://github.com/lionheart/ConcentricProgressRingView
At the top of your UIViewController, import the module:
import ConcentricProgressRingView
Then, in your viewDidLoad:
let rings = [
ProgressRing(color: UIColor.redColor()),
ProgressRing(color: UIColor.blueColor()),
]
let margin: CGFloat = 2
let radius: CGFloat = 80
let progressRingView = ConcentricProgressRingView(center: view.center, radius: radius, margin: margin, rings: rings, defaultWidth: 20)
view.addSubview(progressRingView)
Once you've instantiated your ConcentricProgressRingView instance, animate a specific ring to a percentage with setProgress.
ring.arcs[1].setProgress(0.5, duration: 2)
Under the hood, this just uses CABasicAnimation and sets a few parameters which make it look "right". I know you're not using Swift, but if you want specific pointers, just check out the source code to see how I've solved it. Hope this helps!
You want to use CAShapeLayers, one for each arc, with a full-circle arc installed in each one, and the line cap style set to rounded.
Then you want to set the strokeEnd property to a small value (try .05). That will cause the shape to only draw 5% of it's arc.
Finally you want to submit CAAnimations to each shape layer that animate the strokeEnd value from the starting value to 1.0. That will animate the arcs drawing the full circle.
I don't have any ready-made sample code for you. I do have a sample project written in Objective-C that uses a very similar technique to create a "clock wipe" animation using a CAShapeLayer as a mask layer.
Take a look at the clock wipe animation in the project "iOS CAAnimationGroup demo" project on Github.
I'm trying to animate an object along a specific segment of a path using CAKeyframeAnimation. Is this possible?
Think of a path as parameterized between 0 and 1, which is what an animation will typically run on (from the start of the curve to the end of the curve). Is there a way I can run an animation, say, from .25 to .75 of this path?
I know I can offset an animation with the 'offset' parameter, but this only adjusts the start position and still loops around to the end of the curve. It also doesn't correctly obey the CAMediaTiming function, if set.
I'm animating some particles and rather than have then just disappear at the end of their lifetime I'd like them to fade out.
I have a CAEmitterCell defined with a lifetime of 35.0. I don't want to just have the particle fade out over the full duration of the particle lifetime. I only want it to fade out at the end. Perhaps the last 2 or 3 seconds.
For the CAEmitterCell's color property, set the alpha value to lifetime * alphaSpeed (where alphaSpeed is -1.0/fadeOutDuration).
So for a lifetime of 35.0 and a fadeOutDuration of 2.0, alphaSpeed would be -0.5, and alpha would be 17.5.
There are a couple caveats:
This only works if your cells are supposed to start at full alpha.
You'll have to set CAEmitterCell's color property using a CGColorRef created with CGColorCreateCopyWithAlpha. Both UIColor and CGColorCreate clamp their values to a maximum of 1.0. For whatever reason, CGColorCreateCopyWithAlpha doesn't.
Just came across this in the docs which might point in the right direction:
name
The name of the cell.
#property(copy) NSString *name
Discussion
The cell name is used when constructing animation key paths that reference the cell. Defaults to nil.
For example, adding an animation to a cell’s enclosing layer with the a keypath such as emitterCells.myCellName.redRange would animate the redRange propery of the cell in the layer’s emitterCells array with the name myCellName.
Availability
Available in Mac OS X v10.6 and later.
Declared In
CAEmitterCell.h
I gather you still add the animation to the layer but with a key path that references a part of that layer - in this case the cell's property. Is there an alpha property exposed for cells?