How to get custom shape using UIBezierPath and make it as subview - ios

I am drawing a rect using UIBezierPath. The following code creates the rect in my view controller:
#interface ViewController ()
{
TargetAreaView *targetView; //TargetAreaView is a subclass of UIView
UIView *detectionView;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGRect frame = self.view.frame;
frame.size.height = frame.size.height - 200;
detectionView = [[UIView alloc]initWithFrame:frame];
targetView = [[TargetAreaView alloc]initWithFrame:detectionView.frame];
targetView.backgroundColor = [UIColor clearColor];
[self.view addSubview:targetView];
// Do any additional setup after loading the view, typically from a nib.
}
I am using the bezier path to draw the rect in TargetAreaView class.
I have used the following code in the UIView subclass:(TargetAreaView)
#interface TargetAreaView : UIView
{
NSMutableArray *points;
CGRect superViewFrame;
CGPoint selectedPoint;
}
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
superViewFrame = frame;
points = [[NSMutableArray alloc]init];
CGPoint startPoint = CGPointMake(superViewFrame.size.width / 2 - 200/2, superViewFrame.size.height / 2 - 200/2);
CGPoint heightPoint1 = CGPointMake(startPoint.x, startPoint.y + 200);
CGPoint widthPoint1 = CGPointMake(startPoint.x + 200, startPoint.y + 200);
CGPoint heightPoint2 = CGPointMake(startPoint.x + 200, startPoint.y);
CGPoint widthPoint2 = CGPointMake(startPoint.x, startPoint.y);
[points addObject:[NSValue valueWithCGPoint:startPoint]];
[points addObject:[NSValue valueWithCGPoint:heightPoint1]];
[points addObject:[NSValue valueWithCGPoint:widthPoint1]];
[points addObject:[NSValue valueWithCGPoint:heightPoint2]];
[points addObject:[NSValue valueWithCGPoint:widthPoint2]];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
if (points && points.count > 0)
{
[self drawTargetArea:points];
[self setNeedsDisplay];
}
}
-(void)drawTargetArea:(NSMutableArray *)savedPoints
{
UIBezierPath *aPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
for (int idx = 0; idx < savedPoints.count; idx++)
{
if (idx == 0)
{
NSValue *startPoint = [savedPoints objectAtIndex:idx];
[aPath moveToPoint:startPoint.CGPointValue]; // Starting Point
}
else
{
NSValue *drawPoints = [savedPoints objectAtIndex:idx];
[aPath addLineToPoint:drawPoints.CGPointValue]; // Add lines from previous to current point
}
}
[aPath closePath]; // close the final path
aPath.lineWidth = 10.0f;
[[UIColor redColor] setStroke];
[aPath stroke];
}
The above UIView subclass code generates the rect as shown below:
I have written the logic to move the corners of rect to change its shape as shown below:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint startTouchPoint = [touch locationInView:self]; //get the user touch position and check it is nearer in 10px range to any of the position in rect
for (NSValue *aPoint in points)
{
CGPoint currentPoint = aPoint.CGPointValue;
CGFloat distance = [self touchPositionDifference:startTouchPoint WithCornerPosition:currentPoint]; // Calculate the distance between user touch position and rect points
if (distance < 10)
{
selectedPoint = currentPoint; // get the nearest point in a global variable
break;
}
}
}
-(CGFloat)touchPositionDifference:(CGPoint)touchPosition WithCornerPosition:(CGPoint)cornnerPosition
{
CGFloat dx= touchPosition.x - cornnerPosition.x;
CGFloat dy= touchPosition.y - cornnerPosition.y;
CGFloat distance= sqrt(dx*dx + dy*dy);
return distance; // calculate and return distance between two points
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchEndPoint = [touch locationInView:self];
for (int idx = 0; idx < points.count; idx++)
{
NSValue *aPoint = [points objectAtIndex:idx];
CGPoint currentPoint = aPoint.CGPointValue;
if (CGPointEqualToPoint(currentPoint, selectedPoint))
{
[points replaceObjectAtIndex:idx withObject:[NSValue valueWithCGPoint:touchEndPoint]]; // get the global point value and touch ended value and replace the new value with old value
}
}
[self setNeedsDisplay];// and draw the shape with new points
}
I can able to get the output like following
I have return code to get the bezier path as follows:
-(UIBezierPath *)getPath
{
if (points.count <= 0) return nil;
NSArray *oldPoints = points;
UIBezierPath *aPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
CGPoint p1 = [[oldPoints objectAtIndex:0] CGPointValue];
[aPath moveToPoint:CGPointMake(p1.x, p1.y)];
for (uint i=1; i<oldPoints.count; i++)
{
CGPoint p = [[oldPoints objectAtIndex:i] CGPointValue];
[aPath addLineToPoint:CGPointMake(p.x, p.y)];
}
[aPath closePath];
return aPath; //This returns me a bezier path of the already drawn shape
}
I am getting the entire view(within that view i am having the irregular shape View is represented in green color and the shape is represented in red border color) as shown below
I want to get that particular shape alone(the irregular shape) As shown below
How can I achieve this? Am I following the right path?

You can use the following code to draw the above mentioned shape.
- (void)drawCanvas1WithFrame: (CGRect)frame
{
//// Color Declarations
UIColor* color = [UIColor colorWithRed: 1 green: 0 blue: 0 alpha: 1];
//// Bezier Drawing
UIBezierPath* bezierPath = UIBezierPath.bezierPath;
[bezierPath moveToPoint: CGPointMake(CGRectGetMinX(frame) + 77.5, CGRectGetMinY(frame) + 39.5)];
[bezierPath addCurveToPoint: CGPointMake(CGRectGetMinX(frame) + 130.5, CGRectGetMinY(frame) + 14.5) controlPoint1: CGPointMake(CGRectGetMinX(frame) + 130.5, CGRectGetMinY(frame) + 14.5) controlPoint2: CGPointMake(CGRectGetMinX(frame) + 130.5, CGRectGetMinY(frame) + 14.5)];
[bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 122.5, CGRectGetMinY(frame) + 75.5)];
[bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 61.5, CGRectGetMinY(frame) + 110.5)];
[bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 77.5, CGRectGetMinY(frame) + 39.5)];
[UIColor.greenColor setFill];
[bezierPath fill];
[color setStroke];
bezierPath.lineWidth = 1;
[bezierPath stroke];
}

Related

How can we avoid the extra bezier curve line?

Can we avoid the extra bezier curve line after the last point in Bezier graph? Is there any property for that?
We need just end-end curve graph, no extra curve. Please share the solutions/suggestion. Update: Please check the code below,
- (void)drawRect:(CGRect)rect {
UIBezierPath *line = [UIBezierPath bezierPath];
NSValue *value = points[0];
CGPoint p1 = [value CGPointValue];
[line moveToPoint:p1];if (points.count == 2) {
value = points[1];
CGPoint p2 = [value CGPointValue];
[line addLineToPoint:p2];
return line;
}
for (NSUInteger i = 1; i < points.count; i++) {
value = points[i];
CGPoint p2 = [value CGPointValue];
CGPoint midPoint = midPointForPoints(p1, p2);
[line addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)];
[line addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, p2)];
p1 = p2;
}
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.frame = self.bounds;
pathLayer.path = line.CGPath;
pathLayer.strokeColor = self.color.CGColor;
pathLayer.fillColor = nil;
pathLayer.opacity = self.lineAlpha;
pathLayer.lineWidth = self.lineWidth;
pathLayer.lineJoin = kCALineJoinBevel;
pathLayer.lineCap = kCALineCapRound;
[self.layer addSublayer:pathLayer];}
static CGPoint midPointForPoints(CGPoint p1, CGPoint p2) {
return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);}
Thanks!

Wrong touch point location in UIView after rotatation

I'm trying to make a custom button using a UIView and when touched on a specific part of the view perform an action, for example when touching the left part of the view log LEFT and when touching the Right part of the view log RIGHT.
I'm using touchesBegan and touchesEnded to detect touch.
Everything works fine when in portrait mode, however, once rotated to landscape mode it doesn't work, the view is displayed in the right place and the touch on the view is detected but it seems the touch point is not in the correct x,y coordinates so I can't detect the LEFT/RIGHT touches correctly.
Couldn't find any previous answers here that helped me.
Tried looking into CGAffineTransform, but still can't fix it.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"round_directions_button_default.png"]];
self.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
topLeft = CGPointMake(self.frame.origin.x, self.frame.origin.y);
topRight = CGPointMake(self.frame.origin.x + 105, self.frame.origin.y);
bottomLeft = CGPointMake(self.frame.origin.x, self.frame.origin.y + 105);
bottomRight = CGPointMake(self.frame.origin.x + 105, self.frame.origin.y + 105);
NSLog(#"topLeft: x = %f, y = %f", topLeft.x, topLeft.y);
NSLog(#"topRight: x = %f, y = %f", topRight.x, topRight.y);
NSLog(#"bottomLeft: x = %f, y = %f", bottomLeft.x, bottomLeft.y);
NSLog(#"bottomRight: x = %f, y = %f", bottomRight.x, bottomRight.y);
NSLog(#"self.center: x = %f, y = %f", self.center.x, self.center.y);
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"topLeft: x = %f, y = %f", topLeft.x, topLeft.y);
NSLog(#"topRight: x = %f, y = %f", topRight.x, topRight.y);
NSLog(#"bottomLeft: x = %f, y = %f", bottomLeft.x, bottomLeft.y);
NSLog(#"bottomRight: x = %f, y = %f", bottomRight.x, bottomRight.y);
NSLog(#"self.center: x = %f, y = %f", self.center.x, self.center.y);
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:nil];
NSLog(#"touchesBegan: touchPoint: x = %f, y = %f", touchPoint.x, touchPoint.y);
if ([self isPoint:touchPoint inTriangle:topLeft :self.center :topRight]) {
NSLog(#"touchesBegan: UP");
direction = 1;
self.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"round_directions_button_pressed_up.png"]];
} else if ([self isPoint:touchPoint inTriangle:bottomLeft :self.center :bottomRight]) {
NSLog(#"touchesBegan: DOWN");
direction = 2;
self.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"round_directions_button_pressed_down.png"]];
} else if ([self isPoint:touchPoint inTriangle:topLeft :self.center :bottomLeft]) {
NSLog(#"touchesBegan: LEFT");
direction = 3;
self.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"round_directions_button_pressed_left.png"]];
} else if ([self isPoint:touchPoint inTriangle:topRight :self.center :bottomRight]) {
NSLog(#"touchesBegan: RIGHT");
direction = 4;
self.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"round_directions_button_pressed_right.png"]];
} else {
NSLog(#"touchesBegan: NOTHING");
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"round_directions_button_default.png"]];
}
- (BOOL) isPoint:(CGPoint)point inTriangle:(CGPoint)v1 :(CGPoint)v2 :(CGPoint)v3 {
BOOL b1, b2, b3;
b1 = ((point.x - v2.x) * (v1.y - v2.y) - (v1.x - v2.x) * (point.y - v2.y)) < 0.0f;
b2 = ((point.x - v3.x) * (v2.y - v3.y) - (v2.x - v3.x) * (point.y - v3.y)) < 0.0f;
b3 = ((point.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (point.y - v1.y)) < 0.0f;
return ((b1 == b2) && (b2 == b3));
}
in view controller viewDidLoad:
CustomButton *customButton = [[CustomButton alloc] initWithFrame:CGRectMake(1, self.view.bounds.size.height - 106, 105, 105)];
[self.view addSubview:customButton];
Any help will be appreciated.
Thanks
If the size of the button doesn't change with rotation you can set points just once inside initWithFrame and use bounds instead of frame.
topLeft = CGPointMake(0, 0);
topRight = CGPointMake(self.bounds.size.width, 0);
bottomLeft = CGPointMake(0, self.bounds.size.height);
bottomRight = CGPointMake(self.bounds.size.width, self.bounds.size.height);
Then in touchesBegan use [touch locationInView:self]; to get the touch location in the button own coordinate system.
If the size changes, a solution can be to recalculate points on every touch so place the above code inside touchesBegan.
EDIT:
If you want to use local button coordinate you have to calculate the center because self.center contains center point of the view in the superview’s coordinate system.

eraser tool for vector drawings ios

I'm implementing my draw tools like this:
CGPoint CGPointCropByRect1(CGPoint point, CGRect rect)
{
rect = CGRectInset(rect, 5., 5.);
return CGPointMake(MIN(CGRectGetMaxX(rect), MAX(CGRectGetMinX(rect), point.x)), MIN(CGRectGetMaxY(rect), MAX(CGRectGetMinY(rect), point.y)));
}
CGFloat CGPointLengthToPoint1(CGPoint first, CGPoint second)
{
return sqrtf(powf(second.x - first.x, 2.) + powf(second.y - first.y, 2.));
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
_paths = [[NSMutableArray alloc] init];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
UIColor *color = [UIColor blackColor];
CGFloat width = 5.f;
if (_isErase) {
CGContextSetStrokeColorWithColor(ctx, [UIColor clearColor].CGColor);
//CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextSetBlendMode(ctx, kCGBlendModeClear);
}
else {
CGContextSetStrokeColorWithColor(ctx, [color CGColor]);
CGContextSetLineWidth(ctx, width);
CGContextSetLineJoin(ctx, kCGLineJoinRound);
CGContextSetLineCap(ctx, kCGLineCapRound);
}
for(NSArray *array in _paths)
{
size_t index = 0;
CGPoint first = [[array objectAtIndex:index++] CGPointValue];
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, first.x, first.y);
while (index < array.count)
{
CGPoint middlePoint = [[array objectAtIndex:index++] CGPointValue];
if(index < array.count)
{
CGPoint endPoint = [[array objectAtIndex:index++] CGPointValue];
//CGContextAddQuadCurveToPoint(ctx, middlePoint.x, middlePoint.y, endPoint.x, endPoint.y);
CGContextAddLineToPoint(ctx, middlePoint.x, middlePoint.y);
CGContextAddLineToPoint(ctx, endPoint.x, endPoint.y);
}
else
CGContextAddLineToPoint(ctx, middlePoint.x, middlePoint.y);
}
CGContextStrokePath(ctx);
}
CGContextBeginPath(ctx);
BOOL first = YES;
for(NSValue *point in _currentPath)
{
if(first)
CGContextMoveToPoint(ctx, point.CGPointValue.x, point.CGPointValue.y);
else
CGContextAddLineToPoint(ctx, point.CGPointValue.x, point.CGPointValue.y);
first = NO;
}
CGContextStrokePath(ctx);
CGContextRestoreGState(ctx);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
_currentPath = [[NSMutableArray alloc] init];
[_currentPath addObject:[NSValue valueWithCGPoint:[[touches anyObject] locationInView:self]]];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint currentTouch = [[touches anyObject] locationInView:self];
if(!CGRectContainsPoint(self.bounds, currentTouch))
{
if(!_currentPath.count)
return;
currentTouch = CGPointCropByRect1(currentTouch, self.bounds);
CGPoint lastTouch = [[_currentPath objectAtIndex:_currentPath.count - 1] CGPointValue];
[_currentPath addObject:[NSValue valueWithCGPoint:currentTouch]];
[self setNeedsDisplayInRect:CGRectInset(CGRectMake(currentTouch.x, currentTouch.y, lastTouch.x - currentTouch.x, lastTouch.y - currentTouch.y), -10., -10.)];
return;
}
if(_currentPath.count == 0)
{
_currentPath = [[NSMutableArray alloc] init];
[_currentPath addObject:[NSValue valueWithCGPoint:currentTouch]];
return;
}
CGPoint lastTouch = [[_currentPath objectAtIndex:_currentPath.count - 1] CGPointValue];
CGFloat dim = CGPointLengthToPoint1(currentTouch, lastTouch);
if(dim > 5.)
{
[_currentPath addObject:[NSValue valueWithCGPoint:currentTouch]];
[self setNeedsDisplayInRect:CGRectInset(CGRectMake(currentTouch.x, currentTouch.y, lastTouch.x - currentTouch.x, lastTouch.y - currentTouch.y), -10., -10.)];
}
}
-(void)dropPath
{
if(_currentPath.count)
[_paths addObject:[NSArray arrayWithArray:_currentPath]];
[_currentPath release];
_currentPath = nil;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint currentTouch = [[touches anyObject] locationInView:self];
if (!_isErase) {
//CGPoint currentTouch = [[touches anyObject] locationInView:self];
[_currentPath addObject:[NSValue valueWithCGPoint:currentTouch]];
[self dropPath];
[self setNeedsDisplay];
}
else{
[_currentPath removeObject:[NSValue valueWithCGPoint:currentTouch]];
[self dropPath];
[self setNeedsDisplay];
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}
#end
the main idea is- I'm storing my current points in NSArray, and then add it in another array.
How can I create a eraser? I think the main idea is to redraw the lines, but how to implement it? Is there any of examples that will make it clear for me?
The option for using background color doesn't fit.
Thanks
You can try adding this snipper:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, self.path);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, [UIColor clear].CGColor);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextSetAlpha(context, self.lineAlpha);
CGContextStrokePath(context);

iOS How to draw a stroke with an outline

I am looking for an output as in this image Expected Result
I need an outline to my stroke. My code is as follows
- (void)awakeFromNib {
self.strokeArray = [NSMutableArray array];
self.layerIndex = 0;
self.isSolid = false;
self.path = [[UIBezierPath alloc] init];
self.innerPath = [[UIBezierPath alloc] init];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[self.path moveToPoint:p];
[self.innerPath moveToPoint:p];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[self.path addLineToPoint:p];
[self.innerPath addLineToPoint:p];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[self.path addLineToPoint:p];
[self.innerPath addLineToPoint:p];
[self drawBitmap];
[self setNeedsDisplay];
[self.path removeAllPoints];
[self.innerPath removeAllPoints];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}
- (void)drawRect:(CGRect)rect
{
[self.incrementalImage drawInRect:rect];
[self.brushColor setStroke];
self.path.lineWidth = self.brushWidth;
if(self.isEraser)
[self.path strokeWithBlendMode:kCGBlendModeClear alpha:0.0];
else
[self.path stroke];
self.innerPath.lineWidth = self.brushWidth - 10;
[[UIColor clearColor] setStroke];
[self.innerPath strokeWithBlendMode:kCGBlendModeClear alpha:1.0];
}
- (void)drawBitmap
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
if (!self.incrementalImage)
{
CGContextClearRect(context, CGRectMake(0,0,self.bounds.size.width,self.bounds.size.height));
}
[self.incrementalImage drawAtPoint:CGPointZero];
[self.brushColor setStroke];
self.path.lineWidth = self.brushWidth;
if(self.isEraser)
[self.path strokeWithBlendMode:kCGBlendModeClear alpha:0.0];
else
[self.path stroke];
self.innerPath.lineWidth = self.brushWidth - 10;
[[UIColor clearColor] setStroke];
[self.innerPath strokeWithBlendMode:kCGBlendModeClear alpha:1.0];
self.incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
On screen what i get is as per this image, Actual Result
I understand that blend mode 'clear' gives an eraser effect. What i want is that the stroke should have a solid outline on the sides and be clear in the middle. It should not blend into the path right below it.The path below it should still be visible.
How can i achieve this result?
Have a look at this question: Generate a CGPath from another path's line width outline
I guess that's what you need: creating a polygon of the line and stroke that instead of the line. Use CGPathCreateCopyByStrokingPath to get a path of the polygon and stroke it.
OK, since CGPathCreateCopyByStrokingPath seems to be a bit buggy, you could create your own implementation. Something like the following algorithm, which assumes you have a NSArray of CGPoint packed in NSValue objects.
It works pretty well until you start adding short overlapping lines which could of course easily happen while drawing. You could reduce this effect by only adding points in touchesMoved that have a larger distance (lineWidth/2?) to the previous added point. Another drawback is that the lines are simply straight (no kCGLineJoinRound or kCGLineCapRound) but maybe you could adjust the algorithm to do that.
+ (UIBezierPath*)polygonForLine:(NSArray *)points withLineWidth:(CGFloat)lineWidth{
UIBezierPath *path = [UIBezierPath bezierPath];
//stores the starting point to close the path
CGPoint startPoint;
//get the points to a c-array for easier access
CGPoint cpoints[points.count];
int numPoints = 0;
for(NSValue *v in points){
cpoints[numPoints++] = [v CGPointValue];
}
//store the last intersection to apply it for the next segement
BOOL hasIntersection = NO;
CGPoint intersectionPoint;
for (int i=0;i<numPoints-1;i++){
//get the current line segment
CGPoint p1 = cpoints[i];
CGPoint p2 = cpoints[i+1];
CGPoint l1p1,l1p2;
getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l1p1,&l1p2);
//if there had been an intersection with the previous segement, start here to get a nice outline
if(hasIntersection){
l1p1 = intersectionPoint;
}
//is there a next segment?
if(i+2<numPoints){
//get the next line segment
p1 = cpoints[i+1];
p2 = cpoints[i+2];
CGPoint l2p1,l2p2;
getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l2p1,&l2p2);
//calculate the intersection point with the current line segment
hasIntersection = getLineIntersection(l1p1, l1p2, l2p1, l2p2, &intersectionPoint);
//if they intersect, the current linesegment has to end here to get a nice outline
if(hasIntersection){
l1p2 = intersectionPoint;
}
}
//write the current linesegment to the path
if(i==0){
//first point, move to it and store it for closing the path later on
startPoint = l1p1;
[path moveToPoint:startPoint];
}else{
[path addLineToPoint:l1p1];
}
[path addLineToPoint:l1p2];
}
//now do the same for the other side of the future polygon
hasIntersection = NO;//reset intersections
for (int i=numPoints-1;i>0;i--){
//get the current line segment
CGPoint p1 = cpoints[i];
CGPoint p2 = cpoints[i-1];
CGPoint l1p1,l1p2;
getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l1p1,&l1p2);
//if there had been an intersection with the previous segement, start here to get a nice outline
if(hasIntersection){
l1p1 = intersectionPoint;
}
//is there a next segment?
if(i-2>=0){
//get the next line segment
p1 = cpoints[i-1];
p2 = cpoints[i-2];
CGPoint l2p1,l2p2;
getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l2p1,&l2p2);
//calculate the intersection point with the current line segment
hasIntersection = getLineIntersection(l1p1, l1p2, l2p1, l2p2, &intersectionPoint);
//if they intersect, the current linesegment has to end here to get a nice outline
if(hasIntersection){
l1p2 = intersectionPoint;
}
}
//write the current linesegment to the path
[path addLineToPoint:l1p1];
[path addLineToPoint:l1p2];
}
//close the path
[path addLineToPoint:startPoint];
//we're done
return path;
}
void getOffsetLineSegmentForPoints(CGPoint p1, CGPoint p2, CGFloat lineWidth, CGPoint *linep1, CGPoint *linep2){
CGPoint offset = CGPointSub(p2, p1);
offset = CGPointNorm(offset);
offset = CGPointOrthogonal(offset);
offset = CGPointMultiply(offset, lineWidth/2);
(*linep1) = CGPointAdd(p1, offset);
(*linep2) = CGPointAdd(p2, offset);
}
CGPoint CGPointSub(CGPoint p1, CGPoint p2){
return CGPointMake(p1.x-p2.x, p1.y-p2.y);
}
CGPoint CGPointAdd(CGPoint p1, CGPoint p2){
return CGPointMake(p1.x+p2.x, p1.y+p2.y);
}
CGFloat CGPointLength(CGPoint p){
return sqrtf(powf(p.x,2)+powf(p.y,2));
}
CGPoint CGPointNorm(CGPoint p){
CGFloat length = CGPointLength(p);
if(length==0)
return CGPointZero;
return CGPointMultiply(p, 1/length);
}
CGPoint CGPointMultiply(CGPoint p, CGFloat f){
return CGPointMake(p.x*f, p.y*f);
}
CGPoint CGPointOrthogonal(CGPoint p){
return CGPointMake(p.y, -p.x);
}
BOOL getLineIntersection(CGPoint l1p1, CGPoint l1p2, CGPoint l2p1,
CGPoint l2p2, CGPoint *intersection)
{
CGPoint s1 = CGPointSub(l1p2, l1p1);
CGPoint s2 = CGPointSub(l2p2, l2p1);
float determinant = (-s2.x * s1.y + s1.x * s2.y);
if(determinant==0)
return NO;
CGPoint l2p1l1p1 = CGPointSub(l1p1, l2p1);
float s, t;
s = (-s1.y * l2p1l1p1.x + s1.x * l2p1l1p1.y) / determinant;
t = ( s2.x * l2p1l1p1.y - s2.y * l2p1l1p1.x) / determinant;
if (s >= 0 && s <= 1 && t >= 0 && t <= 1){
if (intersection != NULL){
(*intersection).x = l1p1.x + (t * s1.x);
(*intersection).y = l1p1.y + (t * s1.y);
}
return YES;
}
return NO;
}

How can I create a movable and resizable polygon with over 1000 sides/lines?

Right now I'm using UIBezierPath and moveToPoint/addLineToPoint inside a view's drawRect.
This same view receives touchesMoved from the viewController. It modifies the posx and posy variables that are used when I draw the polygon, like this:
[path addLineToPoint:CGPointMake([p.getx floatValue]+posx, [p.gety floatValue]+posy)]
Unfortunately the performance is horrible and the polygon leaves a trail whenever I move it.
What's the best way to achieve what I'm trying to do?
edit: the drawRect. polys is a NSMutableArray with poly objects. Each poly is one x/y point.
- (void)drawRect:(CGRect)rect{
UIBezierPath* path;
UIColor* fillColor;
path = [UIBezierPath bezierPath];
for (int i = 0; i < [polys count]; i++){
poly *p = [polys objectAtIndex:i];
if (i == 0){
[path moveToPoint:CGPointMake([p.getx floatValue]+posx, [p.gety floatValue]+posy)];
}else{
[path addLineToPoint:CGPointMake([p.getx floatValue]+posx, [p.gety floatValue]+posy)];
fillColor = [UIColor blueColor]; // plan to use a random color here
}
}
[path closePath];
[fillColor setFill];
[path fill];
}
Haven't figured out your problem. My guess is that you want to draw the polygon with user's finger. I have this small class that works perfectly, probably it can help:
#implementation View {
NSMutableArray* _points;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
self.backgroundColor = [UIColor whiteColor];
_points = [NSMutableArray array];
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Clear old path
[_points removeAllObjects];
UITouch* touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[_points addObject:[NSValue valueWithCGPoint:p]];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[_points addObject:[NSValue valueWithCGPoint:p]];
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
UIBezierPath* path = [UIBezierPath bezierPath];
for (int i = 0; i < _points.count; i++){
CGPoint p = [_points[i] CGPointValue];
if (i == 0){
[path moveToPoint:p];
}
else {
[path addLineToPoint:p];
}
}
[path closePath];
UIColor* color = [UIColor blueColor];
[color setFill];
[path fill];
}
#end
Just add the view somewhere in your app, maybe make it full screen.

Resources