Curve line into circle in iOS - ios

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.

Related

How can I create an iOS CAAnimation and define every interpolated value myself?

I want to animate the path of a CAShapeLayer. However, this path is created using many arcs. The number of arcs is the same in the begin and end value of the path. However, since the arcs are of different length, this seems to mean that the actual paths use a different amount of Bezier Control Points. This means that the animation is not predictable and very glitchy*.
My solution so far is to create a CAKeyFrameAnimation, and put in a very high number of keyframes. It only works with a very high number of keyframes per second. (around 200 to 300). Anything lower, and glitches* start to appear.
This approach is very crude as it succes depends on the actual frame rate on the actual device and creates more instances of the key frames than is actually needed for the animation, wasting memory.
So my question is: Is there a way to create an animation where I have to create every value somehow? I am immagining some delegate based approach where I am asked a value based on a key time. I don't know what the best approach is to my problem.
*Note: The glitches I am speaking of are weirdly looking paths that appear for one frame only.
You should try using a CADisplayLink
Here is an example:
func setupAnimation() {
let dispLink = CADisplayLink(target: self, selector: #selector(updateAnimation))
dispLink.preferredFramesPerSecond = 60 //will update 60 times per second (the default for iOS devices) ... this line of code is optional depending on the fps you want for your animation
dispLink.add(to: RunLoop.current, forMode: RunLoop.Mode.common)
}
func updateAnimation() {
//do your animation code here
}
Using a display link will link your animation to the refresh rate of the screen that way it's smooth and you can basically do anything you want without it looking choppy
UIBezierPath, and it's underlying Core Foundation class, CGPath, do indeed use variable numbers of control points when creating an arc depending on the angle of the arc.
Thus, as you've discovered, if you're trying to animate a path that includes arcs with different angles, you get very strange drawing artifacts as the number of control points in the path changes.
I suggest using Catmull-Rom splines to create your arcs instead. A Catmull-Rom spline is a different kind of cubic curve where all the control points are on the curve. I learned about Catmull-Rom splines from the outstanding iOS Developers' Cookbook series by Erica Sadun (highly recommended reading.)
If you create a Catmull-Rom spline with about 10 points evenly spaced around a circle, the resulting curve you get will very closely approximate a circle. (I initially recommended 8 control points, but I just tried it, and a curve that describes a full circle doesn't look perfectly round until you have at least 10 control points.) If you want to generate a CGPath that contains an arc with a smaller angle, generate 10 control points that cover less or more of your arc.
I have a project on Github called TrochoidDemo that uses Catmull Rom Splines to create realistic looking water waves. You can ignore most of it, but it includes a file SmoothCGPointsArray.swift that will take an array of control points and use Catmull-Rom splines to return a new array with intermediate points that describe a smooth curve between your control points.
The function you want in that file is smoothPointsInArray(_:granularity:adjustGranularity:)
The adjustGranularity parameter defaults to true. That parameter tells the function to vary the number of smoothing points it creates depending on the distance between control points. You will want to override the default value and call the function with adjustGranularity=false, since for animation you always want the same number of points.
You would just call that function with an array of control points that includes 10 points for each arc you want to draw, regardless of angle. It would return an array of CGPoints, and you would then use the resulting array of GCPoints to generate a UIBezierPath using lineTo() commands, and then feed the CGPath from your UIBezierPath to your animation.
In your source array of points, if you want a given point to be a sharp corner, add that point twice in a row. If you want a line segment, add both endpoints of the line segment twice.
Here is what an 8-point approximation of a circle looks like:
And here is what a 10-point approximation of a circle looks like:
(In each image above the Catmull-Rom approximation is drawn in black, with the control points shown in blue, where the Cocoa-drawn circle is drawn in blue. Note that Cocoa uses Bezier curves to create it's circles, so Cocoa circles are also approximations.)

Circle with fluid/strechy stroke

Me and my team are working on an app for a client. We are trying to understand how to achieve this kind of animations (refer only to the circle stroke) :
We tried using a CADisplayLink to set up and change the circle, but it generated non-fluid results.
We couldn't find a way to create a circle from "components" of UIBezierPath and change each of the anchors.
Any suggestions on how to achieve this kind of effect, or how to construct a circle from seperated points, would be highly appricated
Best Regards,
Roi and the team
I suggest using Catmull-Rom splines. Those allow you to create smooth curves using only points that are on the curve, whereas Bezier curves require that you define control points that are not on the curve.
Once you have beginning and ending CGPaths its pretty easy to create a CAAnimation of the path from it's starting to it's ending state (although animating change to a CGPath only works correctly if the starting and ending paths in the animation have the same number and type of points.)
You could probably also use Bezier curves, but you would need to generate the control points for the circle and it's distorted shape.
Check out this sample app that uses Catmull-Rom splines to create a distorted circle shape:
http://wareto.com/animating-shapes-using-cashapelayer-and-cabasicanimation
(Written in Objective-C, but the technique is the same in Swift.)
A Catmull-Rom spline with 8 control points evenly spaced around a circle where the distance from the center of each control points is varied by ± r/12 seems about right:

Moving Tail Animation

I want to make an animation with a moving line, similar to a dog waging his tail.
I'm not sure how to begin. I've used Core Animation's CAShapeLayer for progress bars before but don't know if it would work for this. I also used PaintCode in the past to help for custom paths, for example to create a custom UIBezierPath for an object to animate on. But also not sure if PaintCode would help with this.
Any ideas?
This wouldn't be that hard. Yes, using a CAShapeLayer animation would be the way to go.
I would make the non-changing part of the image into a bitmap, and then draw your shape layer animation on top.
The trick with shape layer animation is that the shape you use needs to have the same number of control points for all the parts of your animation.
In the case of your tail animation you should be able to map it out as a set of quadratic bezier curves. You could probably draw the tail as a single thick path with a rounded end cap. (kCGLineCapRound). You'd make the starting curve a quadratic bezier curve with the starting point down at the beginning of the straight part of the tail, the next control point slightly to the right and below the curved tip, the next control point above and to the left of the first control point but still below and to the right of the tip, and the last control point at the tip.
The ending curve would have the first control point at the same place on the base of the tail, the second control point at about the top of the curve and to the left, the next control point above the curve and about 2/3 of the way between the base point and the tip, and the last point at the tip of the tail.
You might want to play with the path tool in Adobe Photoshop (or GIMP) and use it to create the starting and ending tail curve shapes using a single quadratic Bezier curve, then write down the control point positions that you use and enter them into your code.
You'd create a CABasicAnimation of the CAShapeLayer where you set the fromValue to the starting CGPath and the toValue to the ending CGPath. Just make sure that the starting and ending paths have the same number of control point.
I have a project called RandomBlobs (link) on Github that shows how to animate a curve using CGPaths and CABasicAnimations, but it is written in Objective-C, and instead of using quadratic bezier curves, it creates another kind of a curve called a Catmull-Rom spline. quadratic bezier curves are actually much simpler to set up than Catcall-Rom splines however, and the idea behind animating changes to a CAShapeLayer using CABasicAnimation should be fairly easy to translate from Objective-C to Swift if you've worked with CAShapelayers before. You could also use a series of Catmull-Rom splines to get the tail animation you're after. The advantage of Catmull-Rom splines is that all the control points are on the curve.

How can I visually display points in a UIBezierPath?

I'm trying to change the shape of a CAShapeLayer from a circle to a different shape. Looking at this question:
Smooth shape shift animation
I found the solution but my question is how can I visually see how many points a UIBezierPath has. Is there a way to color a point different than the line it produces?
For example,it's simple with a line to understand that there are two point, but if we make a circle with bezierPathWithRoundedRect, does that count as one point or are there more?
You would need to add the circle yourself to visually see the dots. Just keep track of the CGPoints you give to the path and draw a circle around each one.
Btw.. if you use PaintCode, you can edit the bezier path and see the point -- it's very useful.

Draw curve with varying line width

I know how to draw curve with Core Graphic or using UIBezierPath.
But, I want to draw a curve that is wide to begin with and thin at the end:
I searched many question about curve, bezier path or something similar on Google. But i can't find any ideal for implementing it.
Can you help me ?
The two methods that immediately come to mind are...
Calculating the path
This is probably the most complex. It would involve calculating the path for the entire shape and adding this as a path and filling it.
Using a line method
You create a series of points that will lie along the centre line of your curve. Maybe 5 points between each point.
Then at each point you can use that as a centre point of a line perpendicular to the tangent of the curve at the point.
The perpendicular line will have a length which you can calculate depending on how far through the curve you are.
Then use this line to create a square path to the line from the previous point.
Then fill that square.
Move to the next point. Add the line and create a box back to the previous line and so on.
At the end are a circle with centre point of the last point. This will create the end.
It's complex but doable if you split down the functions.

Resources