I need to animate arcs (a.k.a donut segments) in the following scenarios where the arc maintains a constant radius r to the imaginary circle center (the arc sits right outside the circle).
1) Animate the arc stroke width from x to y, while maintaining a radius r and angle alpha.
2) Animate the arc angle from alpha to beta while maintaining a constant stroke width and radius.
3) do 1 and 2 together but possibly with independent animations/timings.
Here's what I have so far:
I’ve implemented the arc drawing as a custom view that simply draws the arc with CGContextAddArc. This is fine for a static arc but it doesn’t animate anything.
Also, I know how to draw clipped images with things like [UIBezierPath addClip].
The latter is interesting because I think that for scenario 1, I can achieve the desired effect in two ways: either keep drawing an arc and modify both stroke and radius to maintain the same perceived inner circle radius (which I’m not optimistic about, I’m afraid that the radius will “jiggle”), or draw a segment of a circle that grows in size (maybe by simply modifying the scale with an affine transform) and is then clipped by a static circular mask.
Now, how do I take all these concepts and nail them down into some actual drawing code? I don’t need real code (though that would be fine too), but more like a conceptual approach, like, can I do this all with a UIView with custom drawing, or do we need to talk about custom key animations that I understand involve CALayers and such. In other words, what’s the right architecture to do all this that would be easiest to code while being efficient from a compositing perspective for smooth animation?
You can already do this with a CAShapeLayer by creating the path for the arc and then animate different stroke properties. You could create the path for the full circle and use the strokeStart and strokeEnd properties to only stroke a certain part of the circle. It is worth noting that the shape layer is center stroked so they increase equally inwards and outwards as you increase the line width. To counter this you could either mask it with the same circle shape and double the line width or animate the path so that the radius increases by half of the line width increase so that the inner most point has the same distance to the center at all times.
The first example can be done by animating the lineWidth property and the second can be done by animating the strokeStart and strokeEnd properties
You should implement this using custom animatable properties on a CALayer subclass. This tutorial (with source here) is for creating animated pie charts and looks pretty good. You should be able to modify it for your requirements.
Related
I have a UIButton subclass that I am rounding the corners of. Using either the usual cornerRadius property on its layer, or creating a rounded mask and applying that to the layer, I always get the effect shown in the image below (blown up so you can see it clearly). The top pixel is slightly transparent, making the edge look soft. If I remove the rounded corners, the edge goes back to solid (like the bottom edge in the image), so I know it's not just trying to draw the view between pixels.
Any ideas?
Be sure that the frame and the mask are fully composed of integers not floats, in case use floor or ceil to get the closest integer rounding by low or top.
With frames CGRectIntegral is very helpful. Floats values automatically create a sort of antialiasing while rendering on screen.
What you see is anti-aliasing (on by default) to make the edges look smooth. This is the only way to make rounded corners look smooth and not stair stepped. If you want rough edges, set the allowsEdgeAntialiasing and edgeAntialiasingMask
I have this idea of transforming a straight line into a circle but the offset of a cell drag in a table view.
As I drag the cell, I want the line to curve into a circle around an image.
I've included a picture below to help demonstrate different states with different drag offsets.
Im not sure where to start, was thinking of maybe using UIBezierPath to draw but not sure if that's the best solution.
If you want it to animate then you have your work cut out for you.
Core Animation of curves is based on CGPath objects, which is the underlying Core Foundation class behind UIBezierPath.
The secret to making a curve animate from one shape to another is to use the same number and type of control points. You won't be able to use any of the standard arc or oval shortcuts (which generate more complex bezier curves that look like arcs.)
Instead, you'll have to build an approximation of a circle piecewise out of a linked series of cubic bezier curves. You should be able to get fairly close with 4 linked cubic bezier curves who's endpoints line are at the N/S/E/W compass points of a circle, and the intermediate control points are spaced evenly outside the circle. A couple of years ago I looked up an article on the net for approximating a circle using Bezier points. I would suggest doing some searching on that.
Alternately, I guess you could generate a circle bezier curve using one of the CGPath or UIBezier shortcuts, then deconstruct the resulting path into the primitives that make it up. Erica Sadun's outstanding iOS Developer's Cookbook series includes a recipe that shows how to deconstruct a UIBezier path into it's primitives.
Once you have a set of control points for a circle, you would need to re-map them into control points that make your line. (A Bezier curve always passes through it's beginning and end points, and if you put the inner 2 control points of a Cubic Bezier on a line, it will turn the curve into a line.)
Now you have 2 shapes made up of the same number of bezier curves and the same number of control points: A circle and a line. You can transform the line into the circle or the circle into the line by moving each of the control points to different x/y coordinates.
Then you might be able to apply a linear interpolation between the starting and ending coordinates of your control points. Use the user's drag of the table view to generate a value from 0 to 1, and apply that to your interpolated control point values (at 0.0, your control points would be at their "straight line" position and your curve would draw as a straight line. At 1.0, they'd be at their circle position, and your curve would draw as a circle. At points between, they'd be a fraction of the way between their beginning and ending positions, and you'd get a shape that was between a line and a circle.
Once you have figured out how to generate the control points to create a curve that moves smoothly from a straight line to a circle, you are ready to tackle doing it using Core Animation and a CAShapeLayer.
If that makes sense then you can probably figure out how to do this. If you have no idea what I am talking about they you are probably in over your head.
(I'm a senior Cocoa/iOS developer. I've done a lot of Core Animation and it would probably take me 3 or 4 hours to get what you are after to work, once I had the circle bezier control points to start from.)
Come to think of it, it would probably be a lot simpler to use UIView keyframe animation. That lets you specify an array of control points that ALL lie on the desired curve, and generates a smooth curve from those points.. Best yet, it is a UIView animation, which is a heck of a lot easier to use than CAAnimation.
Take a look at my demo project RandomBlobs on github. That should give you a head start on using UIView keyframe animation. The method you want is called animateKeyframesWithDuration:delay:options:animations:completion:.
The down-side of point-based keyframe animation is that sometimes the curve you get has "kinks" or loops in it that you don't expect or want. You have to avoid sharp bends. In sketching it out, though, I think a line-to-circle transition might work with keyframe view animation.
I have two shapes in a UIView - one, an ellipse and two, a triangle drawn using UIBezierPath. I need to draw the outline of these two shapes combined. How can I do it?
You can do an "outside" stroke (like stroke->outside in photoshop/pixelmator) by calling stroke to draw the outline and setting the inverse of your shapes as the clipping path first. To do the inverse of the clipping path see this answer: https://stackoverflow.com/a/10639523/461492 (read comments too).
So here are the steps:
Set the full area as the clipping path.
Call CGContextEOClip() for each of your shapes as described in the comments to the answer linked above.
Stroke your shapes.
This might not be exactly what you want - it will draw the stroke as normal but the whole interior (the fill area) of your shapes will not be drawn. So whereas the thickness of the stroke would normally extend within the interior of your shapes, and the internal angles of your stroke would normally have the correct corners (rounded/mitered) - in this case it would be more like you stroked the shapes then deleted the fill-area, or did an "outside" stroke in an image editing program.
I have a texture with 250px width and 2000px height. 250x250 part of it drawn on screen according to various conditions (some kind of sprite sheet, yes). All I want is to draw it within a fixed destination rectangle with some rotation. Is it possible?
Yes. Here's how to effectively rotate your destination rectangle:
Take a look at the overloads for SpriteBatch.Draw.
Notice that none of the overloads that take a Rectangle as a destination take a rotation parameter. It's because such a thing does not make much sense. It's ambiguous as to how you want the destination rotated.
But you can achieve the same effect as setting a destination rectangle by careful use of the position and scale parameters. Combine these with the origin (centroid of scaling and rotation, specified in pixels in relation to your sourceRectangle) and rotation parameters to achieve the effect you want.
(If, on the other hand, you want to "fit" to a rectangle - effectively scaling after rotating - you would have to also use the transformMatrix parameter to Begin.)
Now - your question isn't quite clear on this point: But if the effect you are after is more like rotating your source rectangle, this is not something you can achieve with plain ol' SpriteBatch.
The quick-and-dirty way to achieve this is to set a viewport that acts as your destination rectangle. Then draw your rotated sprite within it. Note that SpriteBatch's coordinate system is based on the viewport, not the screen.
The "nicer" (but much harder to implement) way to do it would be to not use SpriteBatch at all, but implement your own sprite drawing that will allow you to rotate the texture coordinates.
in my previous code, I changed the coordinate system in my view's drawRect, so that the rectangle had 0,0 in the centre, 0,1 at the top centre and 1,0 in the centre of the right edge. I.e. a normalised Cartesian system.
{
// SCALE so that we range from TL(0, 0) - BR(2, -2)
CGContextScaleCTM (X, 0.5 * bitmapSize.width, -0.5 * bitmapSize.height);
// TRANSLATE so that we range from TL(-1, 1) - BR(1, -1)
// ie: a cartesian coordinate system, centred on (0, 0) with:
// x increasing to the right
// y increasing upwards
// x&y each ranging from -1 to 1
CGContextTranslateCTM(X, 1, -1);
T = CGContextGetCTM (X);
}
in this box I create a wheel with 12 custom drawn buttons arranged around it. The buttons glow before gradually fading when pressed.
now I am redesigning the code, as the animation was rendering far too slowly
in my view's Load event I create a wheel object, which draws the wheel onto its own CALayer. this is then added as a sublayer to the view's Layer.
( the wheel object will in turn create the buttons which will each draw onto their own CALayer, and these layers will be added as sub layers to the wheels layer )
anyway, I would very much like to perform the drawing of the wheel and the buttons using my normalised Cartesian system. But I can't quite see how to implement it.
I could change the views transform. But this changes the boundary rectangle of the view. One solution would be to have a view within a view, but I discovered by chance testing that clipping to a circular path and drawing within that path will substantially slower than drawing without the clip. So I am hesitant to do this. I am looking for optimal efficiency (without going to GL, just yet -- I'm not ready for that -- I need to understand this stuff first, I think)
alternatively I could change the transform of the layer. but this is a 3D transform! I am having a lot of trouble getting my head around the logic of this. IPhone is a 2D screen. On it is represented a 2D interface. views and layers I conceptualise as flat rectangles sitting on top of one another. Is this wrong? Is this 3-D business just to do funky flip effects?
what if the layer has been set to rasterize, and it has a weird transform? How can it rasterize if it doesn't know what pixel resolution it is running? what if we have 10 nested layers each with a funky transform? And the innermost one needs to be rasterised? Does it somehow go all the way down the chain and figure out what pixels it is to overlay? What if the base layer is within a view within a view within a view and these views have 2D transforms on them? Does it really go all the way until it gets to pixel?
I want to rasterize my buttons -- that's why I'm asking. They are very complex drawing objects. It would save a lot of CPU GPU if they were prerendered dead and alive, and the fading would simply consist of compositing x*dead plus (1-x)*alive. so really I want bitmaps for both.
could someone slay my confusion?
many thanks,
Ohmu