iOS - 2D line drawing on negative coordinates - ios

I am developing an app in which user can draw lines. The desktop version of the same app is able to draw lines on negative coordinates and I would like to have same capability in iOS app too.
What I've tried so far:-
I have a UIViewController inside which I have overridden - (void)drawRect:(CGRect)rect and drawn the line. Here is the code I am using:-
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, self.bounds);
CGPoint startPoint = CGPointMake(self.startHandle.frame.origin.x + self.startHandle.frame.size.width/2, self.startHandle.frame.origin.y + self.startHandle.frame.size.height/2);
CGPoint endPoint = CGPointMake(self.endHandle.frame.origin.x + self.endHandle.frame.size.width/2, self.endHandle.frame.origin.y + self.endHandle.frame.size.height/2);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
CGContextSetLineWidth(context, 2.0f);
CGContextMoveToPoint(context, startPoint.x, startPoint.y ); //start at this point
CGContextAddLineToPoint(context, endPoint.x, endPoint.y); //draw to this point
It works very well until I have startPoint.x as a negative value. With negative value, I don't see the portion of line that is behind 0,0 coordinates. Any help or information is appreciated.

Following on from what #matt said (he has since deleted his answer, shame as it was still useful), you simply want to apply a transform to the context in order to draw in a standard cartesian coordinate system, where (0,0) is the center of the screen.
The context provided to you from within drawRect has its origin at the top left of the screen, with the positive y-direction going down the screen.
So first, we'll want to first flip the y-axis so that the positive y-direction goes up the screen.
We can do this by using CGContextScaleCTM(context, 1, -1). This will reflect the y-axis of the context. Therefore, this reflected context will have a y-axis where the positive direction goes up the screen.
Next, we want to translate the context to the correct position, so that (0,0) corresponds to the center of the screen. We should therefore shift the y-axis down by half of the height (so that 0 on the y-axis is now at the center), and the x-axis to the right by half of the width.
We can do this by using CGContextTranslateCTM(context, width*0.5, -height*0.5), where width and height are the width and height of your view respectively.
Now your context coordinates will look like this:
You could implement this into your drawRect like so:
-(void) drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat width = self.bounds.size.width;
CGFloat height = self.bounds.size.height;
// scale and translate to the standard cartesian coordinate system where the (0,0) is the center of the screen.
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, width*0.5, -height*0.5);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextSetLineWidth(ctx, 2.0);
// add y-axis
CGContextMoveToPoint(ctx, 0, -height*0.5);
CGContextAddLineToPoint(ctx, 0, height*0.5);
// add x-axis
CGContextMoveToPoint(ctx, -width*0.5, 0);
CGContextAddLineToPoint(ctx, width*0.5, 0);
// stroke axis
CGContextStrokePath(ctx);
// define start and end points
CGPoint startPoint = CGPointMake(-100, 100);
CGPoint endPoint = CGPointMake(100, -200);
CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextSetLineWidth(ctx, 5.0);
CGContextMoveToPoint(ctx, startPoint.x, startPoint.y );
CGContextAddLineToPoint(ctx, endPoint.x, endPoint.y);
CGContextStrokePath(ctx);
}
This will give the following output:
I added the red lines to represent the y and x axis, in order to clarify what we've done here.

CGContextClearRect(context, self.bounds);
You can draw out of this bounds,please make a larger rect.

Related

custom annotation view for maps

I am trying to draw an annotation for map , my view is a subclass of MKAnnotationView
I need a shape something like shown below
What I am getting is like this :
Here is the code which I am using :
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx= UIGraphicsGetCurrentContext();
UIGraphicsPushContext(ctx);
CGRect bounds = [self bounds];
CGPoint topLeft = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
CGPoint topRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
CGPoint midBottom = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGFloat height = bounds.size.height;
CGFloat width = bounds.size.width;
//draw semi circle
CGContextBeginPath(ctx);
CGContextAddArc(ctx, width/2, height/2, width/2, 0 ,M_PI, YES);
//draw bottom cone
CGContextAddLineToPoint(ctx, midBottom.x, midBottom.y);
CGContextAddLineToPoint(ctx, topRight.x, topRight.y + height/2); // mid right
CGContextClosePath(ctx);
CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextFillPath(ctx);
UIGraphicsPopContext();
}
You can achieve the desired effect if you replace your lines with quad curves:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx= UIGraphicsGetCurrentContext();
UIGraphicsPushContext(ctx);
CGRect bounds = [self bounds];
CGPoint topLeft = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
CGPoint topRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
CGPoint midBottom = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGFloat height = bounds.size.height;
CGFloat width = bounds.size.width;
//draw semi circle
CGContextBeginPath(ctx);
CGContextAddArc(ctx, width/2, height/2, width/2, 0 ,M_PI, YES);
//draw bottom cone
CGContextAddQuadCurveToPoint(ctx, topLeft.x, height * 2 / 3, midBottom.x, midBottom.y);
CGContextAddQuadCurveToPoint(ctx, topRight.x, height * 2 / 3, topRight.x, topRight.y + height/2);
// CGContextAddLineToPoint(ctx, midBottom.x, midBottom.y);
// CGContextAddLineToPoint(ctx, topRight.x, topRight.y + height/2); // mid right
CGContextClosePath(ctx);
CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextFillPath(ctx);
UIGraphicsPopContext();
}
This employs a quadratic bezier curve, which is a good curve when you don't want inflection points. You can achieve a similar curve with the cubic bezier, but if you're not careful with your control points, you can get undesired inflection points. The quadratic curve is just a little easier with only one control point per curve.
By choosing control points with x values the same as the start and end of the semicircle, it ensures a smooth transition from the circle to the curve leading down to the point. By choosing y values for those control points which are relatively close to the start and end of the semicircle, it ensures that the curve will transition quickly from the semicircle to the point. You can adjust these control points, but hopefully this illustrates the idea.
For illustration of the difference between these two types of curves, see the Curves section of the Paths chapter of the Quartz 2D Programming Guide.

Draw triangle as subview to right corner of table cell

I would like to draw a triangle using CGRect instead of UIImageView and add it as subview to the right corner of some particular table cells similar to how it's done on the WWDC app.
Any advice is welcomed. :)
An easy way can be to have different UIImageView's containing a colored-triangle, then selecting the different image to display according to certain values/preference that you setup.
Taken from here:Drawing a triangle in UIView
You will need to do some math to calculate the correct points, but this is the general way of drawing a triangle. You can just create your own subclass of UIView (call it CornerTriangle) and then add it to the cell while settings its color to match your needs.
-(void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextMoveToPoint (ctx, CGRectGetMinX(rect), CGRectGetMinY(rect)); // top left
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMidY(rect)); // mid right
CGContextAddLineToPoint(ctx, CGRectGetMinX(rect), CGRectGetMaxY(rect)); // bottom left
CGContextClosePath(ctx);
CGContextSetRGBFillColor(ctx, 1, 1, 0, 1);
CGContextFillPath(ctx);
}
Add the following method to your UiTableviewCell Class . This is the right effect you are looking for :
-(void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextMoveToPoint (ctx, CGRectGetMaxX(rect), CGRectGetMidY(rect));
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMinY(rect));
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect)-25, CGRectGetMinY(rect));
CGContextClosePath(ctx);
CGContextSetRGBFillColor(ctx, 1, 1, 0, 1);
CGContextFillPath(ctx);
}

How to fill the area below a path in Quartz 2D?

I am using Quartz 2D in a UIView object to draw several curves, like this example:
This is the code I have right now (I have omitted the calculation of control points and other stuff):
for (int i = 1; i < points.count; ++i) {
CGContextMoveToPoint(context, previousXValue, previousYValue);
CGContextAddCurveToPoint(context,
firstControlPointXValue, firstControlPointYValue,
secondControlPointXValue, secondControlPointYValue,
xValue, yValue
);
// CGContextFillPath(context);
CGContextStrokePath(context);
}
Now I would like to fill the area below the curve, but if I use CGContextFillPath the result is the following:
which makes sense, because according to Quartz 2D documentation:
When you fill the current path, Quartz acts as if each subpath contained in the path were closed. It then uses these closed subpaths
and calculates the pixels to fill.
Then I tried to move the path to the lower right corner and close the path, but the fill method doesn't have any effect:
CGContextMoveToPoint(context, rect.size.width, rect.size.height);
CGContextClosePath(context);
CGContextFillPath(context);
How could I fill the whole area below the curve and not just the subarea closed in every subpath?
EDIT:
I have found a temporary solution: drawing a shape using the curve and two vertical lines on each subpath:
for (int i = 1; i < points.count; ++i) {
// Draw the curve
CGContextMoveToPoint(context, previousXValue, previousYValue);
CGContextAddCurveToPoint(context,
firstControlPointXValue, firstControlPointYValue,
secondControlPointXValue, secondControlPointYValue,
xValue, yValue
);
CGContextStrokePath(context);
// Draw a shape using curve's initial and final points
CGContextMoveToPoint(context, previousXValue, rect.size.height);
CGContextAddLineToPoint(context, previousXValue, previousYValue);
CGContextAddCurveToPoint(context,
firstControlPointXValue, firstControlPointYValue,
secondControlPointXValue, secondControlPointYValue,
xValue, yValue
);
CGContextAddLineToPoint(context, xValue, rect.size.height);
CGContextFillPath(context);
}
I don't know if this is overkill, so improvements are also welcomed. Besides, I'm getting this result:
Note that vertical lines appear as a result of drawing the subareas next to each other. How can that be avoided?
The idea is right, but you should not fill the individual curves, but rather, create a single path and then fill that. So, start with CGContextMoveToPoint to the lower left corner, CGContextAddLineToPoint to your first point, do the CGContextAddCurveToPoint for all of the curves, and at the end, do a CGContextAddLineToPoint to the lower right corner (and you might as well do a CGContextClosePath for good measure).
This is a simplified example, where I just have an array of points, but it illustrates the idea:
- (void)drawRect:(CGRect)rect
{
if (!self.points)
[self configurePoints];
DataPoint *point;
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef color = [[UIColor redColor] CGColor];
CGContextSetStrokeColorWithColor(context, color);
CGContextSetFillColorWithColor(context, color);
point = self.points[0];
CGContextMoveToPoint(context, point.x, self.bounds.size.height);
CGContextAddLineToPoint(context, point.x, point.y);
for (point in self.points)
{
// you'd do your CGContextAddCurveToPoint here; I'm just adding lines
CGContextAddLineToPoint(context, point.x, point.y);
}
point = [self.points lastObject];
CGContextAddLineToPoint(context, point.x, self.bounds.size.height);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}

iPhone Pixel Coordinate Issue

I am trying to make a line graph using the drawRect method but I have came across a major problem. The 0 point on the iPhone screen is in the top left corner. Is there any way to either relocate the 0,0 point or change the coordinates input to where they would be if the 0,0 point was in the bottom left corner?
Heres My drawRect method
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 1.0);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = {0.0, 0.0, 1.0, 1.0};
CGColorRef color = CGColorCreate(colorspace, components);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
//Coordinates of bottom left corner (480 went off screen)
CGContextMoveToPoint(context, 0, 460);
CGContextAddLineToPoint(context, 320, 0);
CGContextStrokePath(context);
CGColorSpaceRelease(colorspace);
CGColorRelease(color);
}
This page describes how to treat it as a right-hand coordinate system instead of a left-hand, which sounds like what you want

Resize UIView to fit a CGPath

I have a UIView subclass on which the user can add a random CGPath. The CGPath is added by processing UIPanGestures.
I would like to resize the UIView to the minimal rect possible that contains the CGPath. In my UIView subclass, I have overridden sizeThatFits to return the minimal size as such:
- (CGSize) sizeThatFits:(CGSize)size {
CGRect box = CGPathGetBoundingBox(sigPath);
return box.size;
}
This works as expected and the UIView is resized to the value returned, but the CGPath is also "resized" proportionally resulting in a different path that what the user had originally drawn. As an example, this is the view with a path as drawn by the user:
And this is the view with the path after resizing:
How can I resize my UIView and not "resize" the path?
Use the CGPathGetBoundingBox. From Apple documentation:
Returns the bounding box containing all points in a graphics path. The
bounding box is the smallest rectangle completely enclosing all points
in the path, including control points for Bézier and quadratic curves.
Here a small proof-of-concept drawRect methods. Hope it helps you!
- (void)drawRect:(CGRect)rect {
//Get the CGContext from this view
CGContextRef context = UIGraphicsGetCurrentContext();
//Clear context rect
CGContextClearRect(context, rect);
//Set the stroke (pen) color
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
//Set the width of the pen mark
CGContextSetLineWidth(context, 1.0);
CGPoint startPoint = CGPointMake(50, 50);
CGPoint arrowPoint = CGPointMake(60, 110);
//Start at this point
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
CGContextAddLineToPoint(context, startPoint.x+100, startPoint.y);
CGContextAddLineToPoint(context, startPoint.x+100, startPoint.y+90);
CGContextAddLineToPoint(context, startPoint.x+50, startPoint.y+90);
CGContextAddLineToPoint(context, arrowPoint.x, arrowPoint.y);
CGContextAddLineToPoint(context, startPoint.x+40, startPoint.y+90);
CGContextAddLineToPoint(context, startPoint.x, startPoint.y+90);
CGContextAddLineToPoint(context, startPoint.x, startPoint.y);
//Draw it
//CGContextStrokePath(context);
CGPathRef aPathRef = CGContextCopyPath(context);
// Close the path
CGContextClosePath(context);
CGRect boundingBox = CGPathGetBoundingBox(aPathRef);
NSLog(#"your minimal enclosing rect: %.2f %.2f %.2f %.2f", boundingBox.origin.x, boundingBox.origin.y, boundingBox.size.width, boundingBox.size.height);
}

Resources