Make independent copy of UIBezierPath? - objective-c

I have a complex UIBezierCurve which I need to draw once with some particular line parameters, and then draw it again as overlay with other line parameters, but also I need the last part of the curve to be slightly shorter than in previous one.
To do this I want to create the curve by addLineToPoint:, moveToPoint: up to the last part, then make a copy of this curve and add the final segments of the line differently in original and copied curves. And then I stroke the original curve, and the copied one.
The problem is that it doesn't work as I expected.
I create a copy of the curve by:
UIBezierPath* copyCurve = [originalCurve copy];
And the drawing which I do in the originalCurve after that, is applied also to the copyCurve, so I can't do independent drawing for any of this curves.
What is the reason for this connection between original and copy and how can I get rid of it?
EDIT 1:
A solution which I've found is to create the copy in the following way:
UIBezierPath* copyCurve=[UIBezierPath bezierPathWithCGPath:CGPathCreateMutableCopy(originalCurve.CGPath)];
Since this works properly, maybe the problem is in the immutability of the copy I get with
[originalCurve copy]

Create a new, identical path by using the CGPath.
path2 = [UIBezierPath bezierPathWithCGPath:path1.CGPath];
The CGPath property docs state that:
This property contains a snapshot of the path at any given point in time. Getting this property returns an immutable path object that you can pass to Core Graphics functions.

copy() works fine for me as of Swift 4.
let copiedPath = originalPath.copy() as! UIBezierPath
copiedPath.addLine(...)
The originalPath does not get modified.

In addition to #jrturton answer :-
Alternatively we can use :-
let path = UIBezierPath(ovalIn: pathRect)
let newPath = path.cgPath.copy(strokingWithWidth: strokeWidth, lineCap: .butt, lineJoin: .miter, miterLimit: 0)
Reference

Related

change CAMetalLayer background color

My CAMetalLayer background color is black, even if i'm assigning new color as the backgroundColor property.
Am i missing something? Thanks!
Link to the original project :
https://github.com/audiokit/MetalParticles
This project takes a rather unconventional approach to clearing the drawable's texture each frame: it replaces the textures contents with an array of zeros that is the same size as the texture (width * height * 4). Subsequently, it encodes some compute work that actually draws the particles. This is almost certainly not the most efficient way to achieve this effect, but if you want to make the smallest change that could possibly work (as opposed to experimenting with making the code as efficient as possible), Just fill the blankBitmapRawData array with your desired clear color (near line 82 of ParticleLab.swift).
I have gone through your code and can not see a place where you are setting background Color.
The metal layer is added as a sublayer to it, so you have to set it explicitly.
Add this line at the end of your init method in ParticialLab class and see if it works.
self.backgroundColor = UIColor.redColor().CGColor
I found that self.isOpaque = false was needed on the layer.

Getting the width of a path drawn on a XAML canvas

I'm drawing a canvas programmatically, given a bunch of path data from somewhere else and adding it to the canvas as
// This is actually done more elaborately, but will do for now
PathFigureCollection figures = GetPathFigureCollection();
var path = new Path
{
Data = new PathGeometry { Figures = figures },
Fill = GetFill(),
Stroke = GetStroke(),
StrokeThickness = GetThickness()
};
MyCanvas.Children.Add(path);
Now, I have the canvas in a ScrollViewer, so I want to make sure that I can scroll all the way to reveal the entire path (actually paths - I have several, generated the same way) but no further. I tried this:
var drawingWidth = MyCanvas.Children
.OfType<FrameworkElement>()
.Max(e => Canvas.GetLeft(e) + e.ActualWidth);
MyCanvas.Width = drawingWidth;
This works well for some other elements (the drawing also has a few text blocks and ellipses), but for the paths both Canvas.GetLeft(e) and e.ActualWith (as well as some other things I tried like e.RenderSize.Width and e.DesiredSize.With) all return 0. Since the element that extends farthest to the right is a path, this results in a canvas that is too small.
How do I get the width of the Path elements too?
Ha, found it!
Rewriting the LINQ query as a loop, I could cast paths to Path, and use path.Data.Bounds.Right as the right edge of that element.** I might be able to convert the code back to a LINQ query now that I know what I want to do (I always find them more readable than stateful loops...).
I found this when I, after having perused the link provided by markE where, as a side note, it was stated that
If your design requirements allow more rough approximates, then you will find that cubic Bezier curves are always contained within their control points.
So, if I could find the right-most control point of all the path figures in my path, I would be home. Intellisense did the rest of the job for me :)

NSBezierPath containsPoint: is too sensitive when path not closed

I have a problem with the containsPoint method. I draw some boxes and connectors between the boxes. The connectors are basicly a single curve, based on a single curveToPoint:controlPoint1:controlPoint2 call. When I now try to select this curve/path with the mouse then this is tricky to do. The containsPoint: method seems to be very sensitive. I tried to draw the line bigger (setLineWidth:), but that doesn't seem to help.
Any ideas what I need to do differently?
For a CGPath, you can always create a closed path which is the contour of the stroked path using:
CGPathRef strokedPath = CGPathCreateCopyByStrokingPath(
path, // your original CGPathRef
NULL, // don't transform
10.0, // lineWidth
kCGLineCapButt, // lineCap (default value)
kCGLineJoinMiter, // lineJoin (default value)
0.0 // miterLimit
);
You can read more about path hit testing here (by Ole Begemann) and here (by Rob Napier).
Thanks to David's answer, I can now provide a full answer.
What I needed were three parts.
Convert the NSBezierPath into a CGPath. This can be done as provided in the Apple Documentation. Or you can use the https://github.com/iccir/XUIKit‎ library, which adds the iPhone framework capabilities to the MacOS frameworks.
Use the CGPathCreateCopyByStrokingPath function as suggested by David.
Convert the new CGPath into a NSBezierPath. David's link to Ole Begemann's block was quite helpful to show how to do it. However, XUIKit is once again a step ahead and provides a +(NSBezierPath) bezierPathWithCGPath: function
The result looks like this.
//con as Connector was the starting point
CGPathRef tapTargetPath = CGPathCreateCopyByStrokingPath(con.CGPath, NULL, 4, kCGLineCapButt, kCGLineJoinBevel, kCGLineJoinMiter );
NSBezierPath * hitPath = [NSBezierPath bezierPathWithCGPath:tapTargetPath];

Merging paths in Core Graphics?

I've drawn two circles that overlap. I want to be able to fill and stroke them as a merged new shape.
At the moment, I create the path sequence once, stroke it, then create a copy of it, fill it and add the two identical paths on top of each other, so they appear as a single shape. Is there a better approach or is this ok?
Update: Here is a sample code:
CGMutablePathRef path = CGPathCreateMutable();
CGContextSetStrokeColorWithColor(theContext, strokeColor.CGColor);
CGContextSetLineWidth(theContext, 2);
CGContextSetFillColorWithColor(theContext, fillColor.CGColor);
CGRect rect1 = CGRectMake(0,0, mySize*0.6, mySize*0.6);
CGRect rect2 = CGRectMake(mySize*0.4,0, mySize*0.6, mySize*0.6);
CGPathAddEllipseInRect(path, NULL, rect1);
CGPathAddEllipseInRect(path, NULL, rect2);
CGContextAddPath(theContext, path);
CGContextDrawPath(theContext, kCGPathFillStroke);
CGPathRef pathFill = CGPathCreateCopy ( path );
CGContextAddPath(theContext, pathFill);
CGContextDrawPath(theContext, kCGPathFill);
CGPathRelease(path);
CGPathRelease(pathFill);
As you can see, I create a copy of the original path and draw it on top without the stroke, so in the end it looks like one united shape. Is there a way to avoid creating the duplicate?
Is there a way to avoid creating the duplicate?
Yes: Just don't create it.
Path objects in Core Graphics are paths and nothing else. They have no colors, no patterns, no fill or stroke properties, nothing—just subpaths consisting of moveto, lineto, curveto, and closepath segments.
The fill color, stroke color, line width, etc. are all properties of the graphics state in the context. The current path is also a property of the context (but not of the gstate).
When you add a path to the context, that's all you're doing: Adding the subpaths from the path object into the current path in the context. The original path object remains unchanged; it has no graphics state, and even if it did, the “add subpaths from path to context” operation changes the context, not the path object.
Similarly, filling or stroking the current path of a context only resets the current path of the context; it makes no changes to any path objects you might have used to build up that path. If it did, copying the path when you do would be too late, as the original would have already been changed—but it doesn't, so copying the path is unnecessary.
So, just add the same path object to the current path both times.

Alternative to CGPathGetPathBoundingBox() for iPad (iOS 3.2)

I'm trying to get my head around using QuartzCore to render semi-complex text/gradient/image UITableViewCell composites. Thankfully, Opacity will let me visually build the view and then spit out source code to drop in to cocoa touch. Trouble is, Opacity assumes the code is running on iOS 4, which is a problem if you want to draw Quartz views on an iPad.
For me, the offending method is CGPathGetPathBoundingBox ... would someone mind pointing me to a suitable alternative or workaround to this (presumably simple) method?
If you care to have some context (no pun intended), here you go:
transform = CGAffineTransformMakeRotation(1.571f);
tempPath = CGPathCreateMutable();
CGPathAddPath(tempPath, &transform, path);
pathBounds = CGPathGetPathBoundingBox(tempPath);
point = pathBounds.origin;
point2 = CGPointMake(CGRectGetMaxX(pathBounds), CGRectGetMinY(pathBounds));
transform = CGAffineTransformInvert(transform);
The alternative is to iterate on the points of the path and note down the leftmost, rightmost, upmost, and downmost co-ordinates of the anchor points yourself, then work out origin and size from those numbers.
You should wrap this in a function, and name it something like MyPathGetPathBoundingBox, and use that until you drop support for iOS 3.x. That will make it easy to switch to CGPathGetPathBoundingBox once you can.