Get UIBezierPath Stroke's Outline Path - ios

I have a UIBezierPath stroke, now I want to get the stroke's outline path(not the stroke's path itself), is there a way I could get that? or at least NSLog the UIBezierPath stroke's outline path? Thanks

You can use CGPathCreateCopyByStrokingPath for this.
UIBezierPath *path = ...;
CGFloat lineWidth = 10;
CGPathRef cgStrokedPath = CGPathCreateCopyByStrokingPath(path.CGPath, NULL,
lineWidth, kCGLineCapRound, kCGLineJoinRound, 0);
UIBezierPath *strokedPath = [UIBezierPath bezierPathWithCGPath:cgStrokedPath];

Related

How to draw a triangle shaped UIButton

I really dig the IFTTT bottom right corner button, as shown in the image (bottom right).
I tried playing around with CGContext and UIBezierPath to match the effect, but i simply don't know enough to get this right. Does anyone have some thoughts/code on how to make this happen?
Thanks!
First use a CAShapeLayer to create a mask
CAShapeLayer * shapeLayer = [CAShapeLayer layer];
//Use these to create path
CGMutablePathRef path = CGPathCreateMutable();
// CGPathMoveToPoint
// CGPathAddLines
shapeLayer.path = path;
Then set mask of your button
yourbutton.layer.mask = shapeLayer
yourbutton.masksToBounds = YES;
Example of a view
CAShapeLayer * shapeLayer = [CAShapeLayer layer];
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, nil, 0, CGRectGetHeight(self.testview.frame));
CGPathAddLineToPoint(path, nil, CGRectGetWidth(self.testview.frame), 0);
CGPathAddLineToPoint(path, nil, CGRectGetWidth(self.testview.frame), CGRectGetHeight(self.testview.frame));
CGPathCloseSubpath(path);
shapeLayer.path = path;
self.testview.layer.masksToBounds = YES;
self.testview.layer.mask = shapeLayer;
And screen shot:
Make a square button, put an image of triangle in it. Won't that work too? To make the illusion even better, draw separate images for onclick() event and ontouch(). Disable the default button press and button click animations to make it look real.

Drawing dashed line in Sprite Kit using SKShapeNode and CGPath

I want to draw a dashed line in my sprite kit game, I can use SKShapeNode node to draw a normal line like the following:
UIBezierPath *_path=[UIBezierPath bezierPath];
//1
CGPoint point1 = CGPointMake(100,100);
CGPoint point2 = CGPointMake(150,150);
[_path moveToPoint:point1];
[_path addLineToPoint:point2];
//2
SKShapeNode *line = [SKShapeNode node];
line.path = _path.CGPath;
I tried to set a dashed pattern to UIBezierPath like this:
// adding this code at location 1 or 2 above but no effect
CGFloat dashes[] = {6, 2};
[_path setLineDash:dashes count:2 phase:0];
but the dashed pattern is not applied.
I also tried to create a dashed copy of CGPath directly from UIBezierpath.CGPath property as:
CGFloat dashes[] = {6, 2};
CGPathRef aCGPath= CGPathCreateCopyByDashingPath(_path.CGPath,NULL,0,dashes,2);
line.path = aCGPath;
but also the same.
I really appreciate if someone could explain what is the problem and how can I draw a dashed line between two points by applying dashed cgpath to skshapenode.
Edit: I know for this simple example I could divide the distance between these two points to small fixed distances and moving and drawing dashed line by bezeirpath but consider a free hand path with points came from touches, it is a very complex and inefficient to redraw the path with fixed length points then draw dashes. I wonder if there is a way to apply the dashed pattern to the path and making skshapenode to use it that is my question.
If anyone is still interested in a simple answer to this question:
Use CGPathCreateCopyByDashingPath to create a dashed copy of - [UIBezierCurve CGPath]
CGPathRef CGPathCreateCopyByDashingPath(
CGPathRef path,
const CGAffineTransform *transform,
CGFloat phase,
const CGFloat *lengths,
size_t count
);
and add it to the SKShapeNode's path property.
Example:
// creates a dashed pattern
CGFloat pattern[2];
pattern[0] = 10.0;
pattern[1] = 10.0;
CGPathRef dashed =
CGPathCreateCopyByDashingPath([bezierPath CGPath],
NULL,
0,
pattern,
2);
self.myShapeNode.path = dashed;
CGPathRelease(dashed);
EDIT: For performance, you can add an SKShapeNode to an SKEffectNode and set the shouldRasterize property to YES.
Change the code at location 1 to something like this:
UIBezierPath *_path=[UIBezierPath bezierPath];
CGPoint point1 = CGPointMake(100,100);
CGPoint point2 = CGPointMake(150,150);
CGFloat deltaX = 1;
CGFloat deltaY = 1;
CGPoint tmpPoint = point1;
[_path moveToPoint:point1];
while(tmpPoint.x<point2.x && tmpPoint.y<point2.y){
tmpPoint.x+=deltaX;
tmpPoint.y+=deltaY;
if((tmpPoint.y-point1.y)%2==1){
[_path addLineToPoint:tmpPoint];
}else{
[_path moveToPoint:tmpPoint];
}
}
// If the line is not a 45 degree straight line
// Please modify the while loop accordingly
[_path addLineToPoint:point2];

iOS: UIBezierPath and CAShapeLayer fillRule

I have used both UIBezierPath and CAShapeLayer before. But almost every time in conjunction with filling the object contained within the path with color inside. But I would like this time to fill the color outside of the object contained by the UIBezierPath.
I just wrote and ran the following simple code trying to get myself acquainted with the fillRule property:
CAShapeLayer *myLayer = (CAShapeLayer *) self.layer; //size: 320 X 480
UIBezierPath *testPath = [UIBezierPath bezierPathWithOvalInRect:(CGRect){{100, 100}, 100, 100}]; //a simple circle
myLayer.fillRule = kCAFillRuleNonZero; // have tried this as well: kCAFillRuleEvenOdd;
myLayer.path = testPath.CGPath;
myLayer.fillColor = [UIColor whiteColor].CGColor;
But the color is filled nonetheless inside. What I would like to find out is, how can I fill the color outside of the path? If I am using fillRule wrong here, I would like to know if there is other methods that can achieve this. Thanks in advance.
The main problem is that you can't really fill the outside of a shape, since there's no generic way to define what that means. What you need to do is first plot a path around the "outside" of your shape, and then add the circle as a subpath. How you do that depends on which fill rule you want to use. EvenOdd is the easiest:
CAShapeLayer *myLayer = (CAShapeLayer *) self.layer;
UIBezierPath *testPath = [UIBezierPath bezierPathWithRect:self.bounds];
[testPath appendPath:[UIBezierPath bezierPathWithOvalInRect:(CGRect){{100, 100}, 100, 100}]];
myLayer.fillRule = kCAFillRuleEvenOdd;
myLayer.path = testPath.CGPath;
myLayer.fillColor = [UIColor whiteColor].CGColor;
NonZero is a little bit harder because you have to force the path to be counter-clockwise which isn't an option for most of the UIBezierPath convenience methods:
CAShapeLayer *myLayer = (CAShapeLayer *) self.layer;
UIBezierPath *testPath = [UIBezierPath bezierPathWithRect:self.bounds];
UIBezierPath *counterClockwise = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100) radius:100 startAngle:0 endAngle:M_PI clockwise:NO];
[counterClockwise appendPath:[UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100) radius:100 startAngle:M_PI endAngle:0 clockwise:NO]];
[testPath appendPath:counterClockwise];
myLayer.fillRule = kCAFillRuleNonZero;
myLayer.path = testPath.CGPath;
myLayer.fillColor = [UIColor redColor].CGColor;
Depending on how you're constructing your actual path it may not make a difference either way.
If you haven't seen it already, the winding rules documentation has some nice diagrams that I find helpful.

UIBezierPaths are not kept on screen

I have a custom UIView and in the drawRect method I create a UIBezierPath and render it to the screen. The issue I am having is that the previous UIBezierPath is removed from the view on the next drawRect call?
How can I keep all of these UIBezierPaths on screen?
- (void)drawRect:(CGRect)rect
{
UIBezierPath *path = [UIBezierPath bezierPath];
// Move to centre and draw an arc.
[path moveToPoint:self.center];
[path addArcWithCenter:self.center
radius:self.radius
startAngle:self.startAngle
endAngle:self.endAngle
clockwise:YES];
[path closePath];
path.usesEvenOddFillRule = YES;
[self.colorToRender setFill];
[path fill];
}
Whenever drawRect: is called you have to draw the whole screen of content. You can't append new drawings to what's already there. So you need to store the whole list of beziers and draw all of them.
Store them in an array and then loop over the array and draw each.
Also, you shouldn't really be creating new beziers in drawRect:...
I am not sure if this is the most convenient way but you have to keep reference to old paths and then combine the new one with them.
CAShapeLayer * shapeLayer; //Sublayer of your view
CGMutablePathRef combinedPath = CGPathCreateMutableCopy(shapeLayer.path);
CGMutablePathRef linePath = CGPathCreateMutable();
//Create your own custom linePath here
//No paths drawn before
if(combinedPath == NULL)
{
combinedPath = linePath;
}
else
{
CGPathAddPath(combinedPath, NULL, linePath);
}
shapeLayer.path = combinedPath;
CGPathRelease(linePath);

Draw a pie chart with UIBezierPath

I'm trying to draw a PieChart using UIBezierPath, and I'm pretty close to do so, however, I've got a problem, as you can see on the screenshot attached
Here's the code I'm using :
-(void)drawRect:(CGRect)rect
{
CGRect bounds = self.bounds;
CGPoint center = CGPointMake((bounds.size.width/2.0), (bounds.size.height/2.0));
NSManagedObject *gameObject = [SCGameManager sharedInstance].gameObject;
int playerNumber = 0;
int totalOfPlayers = [(NSSet*)[gameObject valueForKey:#"playerColors"] count];
float anglePerPlayer = M_PI*2 / totalOfPlayers;
for (NSManagedObject *aPlayerColor in [gameObject valueForKey:#"playerColors"]){
//Draw the progress
CGFloat startAngle = anglePerPlayer * playerNumber;
CGFloat endAngle = startAngle + anglePerPlayer;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:self.frame. size.width/2 startAngle:startAngle endAngle:endAngle clockwise:YES];
UIColor *playerColor = [SCConstants getUIColorForPlayer:[[aPlayerColor valueForKey:#"colorIndex"] intValue]];
[playerColor set];
[path fill];
playerNumber++;
}
}
Apparently, I just need to move my Path to the center of the circle, and then close it, but when I'm adding the following line of code :
[path addLineToPoint:self.center];
[path closePath];
It draws something weird :
Do you have any idea of what is going wrong with my code? I'm not a Bezier expert at all, so any help is welcome!
Thanks!
It looks like the center point you're using is the problem. Indeed, if you look at the documentation for the center property of UIView, you'll find:
The center is specified within the coordinate system of its superview and is measured in points.
You want the center point of the view specified in its own coordinate system, not that of its superview. You've already determined the center of the view in its own coordinates here:
CGPoint center = CGPointMake((bounds.size.width/2.0), (bounds.size.height/2.0));
So just change the point you're using as the center point from self.center to center, like this:
[path addLineToPoint:center];

Resources