If I define an open UIBezierPath and set it as a collision boundary:
_containerPath = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:M_PI
endAngle:0
clockwise:NO];
[_collisionBehavior addBoundaryWithIdentifier:#"containerBoundary" forPath:_containerPath];
and then turn gravity on, objects that are released inside the "bowl" respect the lower boundary, but objects released from above the bowl come to rest on the supposedly non-existent side. Is this expected behavior?
In the picture, the red rectangle was dropped from above; the reference view for the dynamic animator is the light gray rect. It fell from above and stopped at the invisible line.
I've confirmed that if you flip the bezier path over, the red rect does in fact respect the curved boundary; I've also tried this using an open (two-sided) triangle instead of curved path - same result.
The behavior you're seeing seems to be the same as what you see for fill with a bezier path. If you draw a "V" and fill it, it behaves as if it were a closed path. With the collision boundaries, you can make an open "V" by adding two lines with addBoundaryWithIdentifier:fromPoint:toPoint:. I don't know it there's any other way around the problem. For your half circle, I presume you could approximate it with a series of straight lines added with the method above. I've approximated circles before using 50 to 100 lines that look very close to what you get with BezierPathWithOvalInRect. I don't know if this creates a serious burden on the system when used as a collision boundary.
Related
I am working on creating a way for a user to move polygons on screen and change their shape by dragging their corner vertices. I then need to be able to re-draw those modified polygons on other devices - so I need to be able to get final positions of all vertices for all polygons on screen.
This is what I am currently doing:
I draw polygons using UIBezierPath for each shape in the override of the drawRect function. I then allow user to drag them around the screen by applying CGAffineTransformMakeTranslation function and the x and y coordinate deltas in touchedMoved function override. I have the initial control points from which initial polygons are drawn (as described here). But once a path instance is moved on screen, those values don't change - so I am only able to get initial values.
Is there something built - in in the Core Graphics framework that will allow me to grab a set of current control points in a UIBezierPath instance? I am trying to avoid keeping track of those points manually. I will consider using other ways to draw if:
there is a built - in way to detect if a point lies within that polygon (such as UIBezierPath#contains method
A way to easily introduce constraints so user can't move a polygon out of bounds of the superview (I need the whole polygon to be visible)
A way to grab all points easily when user is done
Everything can run under 60fps on iPhone 5.
Thanks for your time!
As you're only applying the transform to the view/layer, to get the transformed control points from the path, simply apply that same transform to the path, and then fetch the control points. Something like:
UIBezierPath* copiedPath = [myPath copy];
[copiedPath applyTransform:[view transform]];
[self yourExistingMethodToFetchPointsFromPath:copiedPath];
The way that you're currently pulling out points from a path is unfortunately at the only API available for re-fetching points from an input UIBezierPath. However - you might be interested in a library I wrote to make working with Bezier path's much simpler: PerformanceBezier. This library makes it significantly easier to get the points from a path:
for(NSInteger i=0;i<[path elementCount];i++){
CGPathElement element = [path elementAtIndex:n];
// now you know the element.type and the element.points
}
In addition to adding functionality to make paths easier to work with, it also adds a caching layer on top of the existing API to make the performance hit of working with paths much much smaller. Depending on how much CPU time you're spending on UIBezierPath methods, this library will make a significant improvement. I saw between 2x and 10x improvement, depending on the operations I was using.
When I use CAShapeLayer and create a rectangle shape then the path starts at rectangle's origin (top-left corner) and draws clockwise. Now if I want to draw only part of the shape then I'm using strokeStart and strokeEnd properties. The problem comes when I want to draw a part consisting the path's end points. In this case the path is closed and it starts and ends on rectangle's top-left corner. When I'm setting strokeStart=0.8 and strokeEnd=0.2 I'm hoping it to draw the last section of the path and a bit from the beginning of the path. However, this is not working. Are there any ideas or tricks how to do this?
Update:
Adding an image to clarify what I mean above. I want an animation which draws a small amount of rectangle and that drawn part circles over the rectangle:
The short answer is that I don't think you can do that, at least not with a single path that will draw any of the segments in your examples. I'm pretty sure that strokeStart must be less than strokeEnd.
If you want to draw your last segment you'd need to create a custom rectangle path that started at the lower left corner and wrapped around.
For an app I am creating, I am using a UIBezierPath to store a path. The problem is that this path continually increases in length, and at a point, the app lags and becomes jerky. Because the path is continually increasing in length, I am constantly redrawing the view 100 times a second (if there is a better way to do this, please let me know). At a certain point, the app just becomes extremely jerky. I think it is because it takes too long to stroke the path.
You can see in my drawRect method that the graphics are translated. Very little of the path is on the screen, so is there a way to only stroke the part of the path that is visible? That is what I thought I was doing with the CGContextClip method, but it had no noticeable improvement.
- (void)drawRect:(CGRect)rect {
CGContextRef myContext = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(myContext, 0, yTranslation);
[[UIColor whiteColor] setStroke];
[bPath addLineToPoint:currentPoint];
[bPath setLineWidth:lineWidth];
CGContextClip(myContext);
[bPath stroke];
}
Thank you.
A couple of thoughts:
IMHO, you shouldn't be adding data points at a regular interval at all. You should be adding data points if and only if there are new data points to be added (e.g. touchesMoved or gesture recognizer gets called with UIGestureStateChanged). This reduces the number of points in bezier and defers the point at which performance problems impose themselves upon you. The process should be driven by the touch events, not a timer.
If some of your drawing is off screen, you can probably speed it up by checking to see if either of the points falls within the visible portion of the view (e.g CGRectContainsPoint). You should probably check for intersections of line segments with the CGRect (as it's theoretically possible for neither the start nor ends to be inside the visible rectangle, but for the line segment between them to intersect the rectangle).
You can also design this process so that it determines which segments are visible only when the view port moves. This can save you from needing to constantly iterate through a very large array.
At some point, there are diminishing returns of drawing individual paths versus a bitmap image, even just for the visible portion. Sometimes it's useful to render old paths as an image, and then draw new paths over that snapshot rather than redrawing the whole path every time. For example, I've used approach where when I'm starting a new gesture, I snapshot old image and only draw new gesture on top of that.
Caching is a possible solution: Draw the curve once on an in memory image with transparent background. Update this image only when the curve changes. Overlay this cached image on whatever you are drawing on. It should be cheaper in processing power.
The other possibility is to remove the unneeded points from the bezier curve after determining which ones will affect the current view, then render the resulting bezier curve.
My current goal is to animate the drawing of a picture in my iOS app so that a user could see something like http://www.youtube.com/watch?v=xlDmk5WdaHk happening on their screen.
The acceleration of my drawings vary with time so I've built my animation using CAKeyFrameAnimation and keyTimes. At the moment, I only have one Bezier path which specifies all the points of my drawing, I add the path to my CAShapeLayer object, and then pass the path off to my animation object when I start my animation.
My problem: the completed drawing immediately appears on the screen, and my pen cursor then moves along the picture according to my keyTimes values, instead of having the stroke color filling in tandem with my pen cursor movement on the screen to produce the same effect as the video.
I noticed this SO question, where the recommended solution was to produce a separate path for every point in the animation: How to Animate CoreGraphics Drawing of Shape Using CAKeyframeAnimation
Given that I have 100-300 individual points, is there a more efficient method of producing a time-variant drawing, than creating an array of a couple hundred paths (with a couple hundred values in each path) with the paths in this form:
path0 = point0...point0...point0...point0...
path1 = point0...point1...point1...point1...
path2 = point0...point1...point2...point2...
path3 = point0...point1...point2...point3...
Then taking that array of paths, passing it into myAnimation.values and animating values with my keyTimes? Or is that really the best way to go about this?
Thanks in advance
There certainly is a more efficient way if your points are on a bezier path. CAShapeLayer has two little properties called strokeStart and strokeEnd. If you initially set strokeEnd to zero then your path will not show. At 1.0 it will show completely. Increasing the values from zero to one will trace your curve accordingly. This is an animatable property so synchronize its value with the key times of your other animation and your drawing will appear as the "pen" is moving.
I'm trying to write a finger painting type app. I am starting a path in touchesBegan and adding to that path in touchesMoved. In touchesMoved, I use the following code:
CGContextMoveToPoint(context, lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(context, currentPoint.x, currentPoint.y);
CGContextStrokePath(context);
I call CGContextStokePath so that the path shows up in realtime as the user draws. The problem is that when using low alpha values, I get dots between the successive path segments where the end cap is essentially drawn twice - once for the previous segment, and once for the current segment.
I've tried using different line caps but the result isn't very pretty. I've also tried using the CGContextDrawPath function with all the various constants and I get the same result.
You can see the results here: http://www.idea-asylum.com/pathwithdots/index.html - It shows a line with alpha = 1.0 and one with alpha = 0.2.
Any ideas? Thanks in advance!
First, I hope you're drawing each shape into a separate layer (and I don't mean CALayer, I mean an internal construct unique to your app). This not only simplifies this task, it makes undo more or less painless (just move the last/topmost layer into a different array and hide it, and empty that array when the user draws a new layer).
Second, during the construction of the shape, don't only remember the last point. Create a CGMutablePath when the user begins the shape and add each subsequent point as another lineto. This also lets you keep the path around in that layer, which means you can throw the rendered image away if a low-memory warning arrives and re-create it the next time you need it.
Third, each time you update the shape during its creation, get its area so far, invalidate that section, and redraw all the layers under it as well as the shape being drawn (as it exists so far). That is, redraw the background, clobbering the new shape, and then draw the up-to-date version of the new shape on top.
Once you are constructing the shape as a single path, and stroking that single path in each draw cycle, the intersections between segments will disappear.