MKOverlayRenderer draw line using CGPath - ios

I'm trying to draw a line using MKOverlayRenderer. My overlay renderer's drawMapRect is roughly:
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, x, y) // x, y = starting point
let remainingPoints: [CGPoint] = ... // remaining points
CGPathAddLines(path, nil, remainingPoints, remainingPoints.count)
CGContextAddPath(context, path)
CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor)
CGContextSetLineWidth(context, 2.0)
CGContextStrokePath(context)
This however doesn't work, nothing appears. I also tried stroking with:
CGContextDrawPath(context, .Stroke)
I know my path is defined correctly because if I use .FillStroke, it fills the polygons.
Closing the path using CGPathCloseSubpath(path) doesn't help.

My line width (2.0) was too small the for the zoom level.

Related

Elliptical radial gradient in CGContext?

As far as I can tell you can use two methods to draw gradients in a CGContext, that's drawLinearGradient and drawRadialGradient. What I'm looking for is a way to define an elliptical gradient where I can define x and y radii.
An example of this capability in another environment (SVG).
<RadialGradient id="gradient" cx="50" cy="50" rx="20" ry="40" fx="150" fy="75">
The existing declaration for drawRadialGradient is as follows.
func drawRadialGradient(_ gradient: CGGradient,
startCenter: CGPoint,
startRadius: CGFloat,
endCenter: CGPoint,
endRadius: CGFloat,
options: CGGradientDrawingOptions)
Both start and end radii are scalar values, so all you can do is circles. How can I draw elliptical gradients in a CGContext?
You should be able to scale the context and use CGContextDrawRadialGradient(). If you scale down, there should be no artifacts. Does the following work?
CGContextRef context;
CGGradientRef gradient;
CGGradientDrawingOptions options;
CGPoint center;
CGFloat radiusX;
CGFloat radiusY;
CGFloat radius = MAX(radiusX, radiusY);
CGContextSaveGState(context);
// scale down by the smaller dimension, and translate so the center stays in place
if (radiusX < radiusY) {
CGContextTranslateCTM(context, center.x - (center.x * (radiusX / radiusY)), 0);
CGContextScaleCTM(context, radiusX / radiusY, 1.0);
}
else {
CGContextTranslateCTM(context, 0, center.y - (center.y * (radiusY / radiusX)));
CGContextScaleCTM(context, 1.0, radiusY / radiusX);
}
CGContextDrawRadialGradient(context, gradient, center, 0, center, radius, options);
CGContextRestoreGState(context);
Incidentally, I think this is roughly the behavior if you set the type property of CAGradientLayer to the undocumented and private value of #"radial". It uses the startPoint as the center, and the difference of the startPoint and endPoint to determine the radiusX and radiusY values (i.e. the endPoint defines a corner of the bounding box of the gradient, and the startPoint is the center). It does have odd behavior when you make the start and end point nearly the same, so there is probably more going on there than I have figured out (and probably why Apple never bothered to make it public).
The only thing I can think of would be to apply a scale transform with unequal x and y scale factors to your context before drawing the gradient. That would stretch it out of round, and should make it oval.
There's no "royal road". This facility is not built-in so you'll have to draw every pixel yourself. (There are probably third-party libraries that will do that for you.)

Create Direction Arrows (arrow heads) on polyline in obj c

I have been searching for a quite while but couldn't find any answer to this, anyways, I am working on Google Maps for iOS using Obj C and have drawn routes (polyline) using multiple coordinates provided to me by the server in the form of an array. But the problem is that I want to show arrow heads on that line so that the direction can be seen on the map. Please help.
Here's a function which draws a nice little arrow line. It has some parameters you can tweak:
void TRDrawLineWithArrow(CGContextRef CXT, CGPoint FROMPOINT, CGPoint TOPOINT, CGFloat WIDTH, CGFloat ARROWSIZEMULTIPLE)
{
CGFloat rise = TOPOINT.y - FROMPOINT.y;
CGFloat run = TOPOINT.x - FROMPOINT.x;
// trig
CGFloat length = sqrt(rise*rise + run+run);
CGFloat angle = atan2(rise, run);
// the length of our arrowhead
CGFloat arrowLen = WIDTH*ARROWSIZEMULTIPLE;
// push graphics context
CGContextSaveGState(CXT);
// transform context according to line's origin and angle
CGContextTranslateCTM(CXT, FROMPOINT.x, FROMPOINT.y);
CGContextRotateCTM(CXT, angle);
// draw straight line
CGContextMoveToPoint(CXT, 0, -WIDTH/2.);
CGContextAddLineToPoint(CXT, 0, WIDTH/2.);
CGContextAddLineToPoint(CXT, length-arrowLen, WIDTH/2.);
// draw arrowhead
CGContextAddLineToPoint(CXT, length-arrowLen, (WIDTH*ARROWSIZEMULTIPLE)/2.);
CGContextAddLineToPoint(CXT, length, 0);
CGContextAddLineToPoint(CXT, length-arrowLen, -(WIDTH*ARROWSIZEMULTIPLE)/2.);
CGContextAddLineToPoint(CXT, length-arrowLen, -WIDTH/2.);
CGContextAddLineToPoint(CXT, 0, -WIDTH/2.);
// fill the path
CGContextFillPath(CXT);
// pop graphics context
CGContextRestoreGState(CXT);
}
You would call it from a UIView like this:
CGContextRef cxt = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(cxt, [UIColor blackColor].CGColor);
TRDrawLineWithArrow(cxt, CGPointMake(10,10), CGPointMake(300,100), 5, 3);

Is there a better alternative to CGContext?

I'm creating a funnel shape using CGContext, by first drawing a triangle, followed by a line. I'm implementing this in drawRect of my UIView subclass, but wondering if there is an alternative way of drawing this, because the lines are a bit blurry.
override func drawRect(rect: CGRect) {
let context: CGContextRef = UIGraphicsGetCurrentContext()!
CGContextClearRect(context, rect);
let rectWidth: CGFloat = 15
// Line
CGContextSetRGBStrokeColor(context, 0, 0, 0, 1)
CGContextMoveToPoint(context, rectWidth / 2, 5)
CGContextAddLineToPoint(context, rectWidth / 2, 10)
CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor)
CGContextSetLineCap(context, CGLineCap.Round)
CGContextSetLineWidth(context, 3.0)
CGContextStrokePath(context)
// Triangle
CGContextBeginPath(context);
CGContextMoveToPoint (context, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextAddLineToPoint(context, CGRectGetMidX(rect), 8);
CGContextAddLineToPoint(context, CGRectGetMaxX(rect), CGRectGetMinY(rect));
CGContextClosePath(context);
UIColor.blueColor().setFill()
CGContextFillPath(context);
}
Produces this:
But when I import an image that I designed, the image is crisper (you can tell on an iPhone):
So, is there a better way of achieving this output? The CGContext lines are a bit blurry, so I'd like to know if there is a better alternative and what the pros and cons to those methods are.
Core Graphics is drawing things blurry probably because you told it to. The location of each pair of integer coordinates in the CG coordinate system is exactly on the grid lines — if you draw a one point wide stroke between such coordinates, you get a stroke that straddles the grid line by half a point on either side. Depending on the scale of the bitmap context you're drawing into, that could result in two columns of half-shaded pixels instead of one column of fully-shaded pixels — that is, a blurry line.
In your case, some of the points you're drawing lines between fall on whole-pixel boundaries and some on half-pixel boundaries, so on 1x displays some of the lines will be blurry and on 2x or 3x displays others will be. Read about Points versus Pixels in Apple's docs on drawing, and you'll probably find a way to offset your coordinates that makes the lines all fall where you want them to.

In iOS, arcs are malformed for certain start angles

I use the following code to draw an arc
double radius = 358.40001058578491;
startAngle = 0.13541347644783652;
double center_x= 684;
double center_y = 440;
std::complex<double> start1( std::polar(radius,startAngle) );
CGPoint targetStart1 = CGPointMake(start1.real() + center_x, start1.imag() +center_y);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, targetStart1.x, targetStart1.y);
CGPathAddArc(path, NULL, center_x, center_y, radius, startAngle, 0.785, 0 );
CGContextAddPath(context, path);
CGContextSetLineWidth( context, 30 );
CGContextSetStrokeColorWithColor( context, targetColor.CGColor);
CGContextStrokePath(context);
CGPathRelease(path);
If u check it in retina, it looks like this:
My arc is the green arc. I have shown the place that the start angle is with a orange line. As I have shown in the red rectangle, there is an extra thing drawn in the very beginning of the arc. This happens not for all start angles, but only for certain start angles.
Do you have any idea why it happens?
Thanks.
In your original question, you specified a literal starting point that was not quite right and, as a result, Core Graphics will draw a line from that point to the start of the arc. And because that starting point was just a few pixels away from the actual start of the arc, it results in that curious rendering you illustrate in your question.
In your revised question, you're calculating the starting point, but I might suggest calculating it programmatically like so:
CGFloat centerX = 684.0;
CGFloat centerY = 440.0;
CGFloat radius = 360.0;
CGFloat startAngle = 0.135;
CGFloat endAngle = 0.785;
CGFloat startingX = centerX + radius * cosf(startAngle);
CGFloat startingY = centerY + radius * sinf(startAngle);
CGContextMoveToPoint(context, startingX, startingY);
CGContextAddArc(context, centerX, centerY, radius, startAngle, endAngle, 0);
CGContextSetLineWidth(context, 30);
CGContextSetStrokeColorWithColor(context, targetColor.CGColor);
CGContextStrokePath(context);
When I calculated it this way, there was no rounding errors that resulted in the artifact illustrated in your original question.
Note, if you're not drawing anything before the arc, you can just omit the CGContextMoveToPoint call altogether. You only need that "move to point" call if you've drawn something before the arc and don't want the path connecting from that CGContextGetPathCurrentPoint to the start of the arc.

Assertion failed when use bodyWithPolygonFromPath: method to egg shape

I am simulating physics to an egg shape SKSprite node.
However, there is assertion failed error occur:
Assertion failed: (edge.LengthSquared() > 1.19209290e-7F * 1.19209290e-7F), function Set, file /SourceCache/PhysicsKit_Sim/PhysicsKit-6.5.4/PhysicsKit/Box2D/Collision/Shapes/b2PolygonShape.cpp, line 180.
the following is the code:
self.egg = [SKSpriteNode spriteNodeWithImageNamed:IMAGE_NAME_EGG];
[self.egg setScale:0.2];
self.egg.position = CGPointMake(self.size.width/2,self.size.height - self.egg.size.height/2);
self.egg.name = IMAGE_NAME_EGG;
CGPoint startPoint = CGPointMake(0, self.egg.size.height*0.4);
CGPoint endPoint = CGPointMake(self.egg.size.width, startPoint.y);
CGPoint controlPointLeft = CGPointMake(startPoint.x, self.egg.size.height);
CGPoint controlPointRight = CGPointMake(endPoint.x, controlPointLeft.y);
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathMoveToPoint(pathRef, NULL, startPoint.x, startPoint.y);
CGPathAddQuadCurveToPoint(pathRef, NULL, controlPointLeft.x, controlPointLeft.y, self.egg.size.width/2, controlPointLeft.y);
CGPathAddQuadCurveToPoint(pathRef, NULL, controlPointRight.x, controlPointRight.y, endPoint.x,endPoint.y);
CGPathAddArc(pathRef, NULL, self.egg.size.width/2, startPoint.y, self.egg.size.width/2, 0, M_PI, NO);
self.egg.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:pathRef];
self.egg.physicsBody.dynamic = YES;
self.egg.physicsBody.categoryBitMask = eggCategory;
self.egg.physicsBody.contactTestBitMask = rabbitCategory;
self.egg.physicsBody.collisionBitMask = rabbitCategory;
self.egg.physicsBody.allowsRotation = YES;
[self addChild:self.egg];
What's wrong? can anyone help me to fix? thank you very much!
when you use " bodyWithPolygonFromPath: ", you mut make sure that the path meats the following conditions:
1). convex polygonal path
2). counterclockwise winding
3). no self intersections.
//The points are specified relative to the owning node’s origin.
you can find it in apply class reference for SKPhysicsBody here:
https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKPhysicsBody_Ref/Reference/Reference.html#//apple_ref/occ/clm/SKPhysicsBody/bodyWithPolygonFromPath:
According to the assertion, one of the edges is shorter than the minimum size expected by sprite kit.
Assertion failed: (edge.LengthSquared() > 1.19209290e-7F * 1.19209290e-7F)
Check your path coordinates and ensure it's not too small.
I had this error on an older iOS7 iPhone when attempting to make a triangular path. It turns out that adding a final line to bring the path back to the starting point led to the error (though newer iOS versions allow this.) IE:
CGPathMoveToPoint(path, nil, self.size.width/2, self.size.height/2)
CGPathAddLineToPoint(path, nil, -self.size.width/2, 0.0)
CGPathAddLineToPoint(path, nil, self.size.width/2, -self.size.height/2)
CGPathAddLineToPoint(path, nil, self.size.width/2, self.size.height/2) //REMOVED THIS LINE TO FIX
CGPathCloseSubpath(path)
This explains why there was an edge that was too small to evaluate - the edge between two of the same points is 0!

Resources