Draw circle with hole by using MKOverlayRenderer does not work well - ios

I'd like to draw circle with hole (like donut) on mapview.
my code is here.
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CG ContextRef)context {
WPCircleOverlay * circleOverlay = self.overlay;
CGPoint centerPoint = [self pointForMapPoint:MKMapPointForCoordinate(circleOverlay.coordinate)];
CGFloat innerRadius = MKMapPointsPerMeterAtLatitude(circleOverlay.coordinate.latitude) * circleOverlay.innerRadius;
CGFloat outerRadius = MKMapPointsPerMeterAtLatitude(circleOverlay.coordinate.latitude) * circleOverlay.outerRadius;
CGMutablePathRef path = CGPathCreateMutable();
//CGPathMoveToPoint(path, ...);
CGPathAddArc(path, NULL, centerPoint.x, centerPoint.y, outerRadius, 0, 2 * M_PI, true);
CGPathCloseSubpath(path);
// Add the inner arc to the path (later used to substract the inner area)
CGPathAddArc(path, NULL, centerPoint.x, centerPoint.y, innerRadius, 0, 2 * M_PI, true);
CGPathCloseSubpath(path);
// Add the path to the context
CGContextAddPath(context, path);
CGContextSetFillColorWithColor(context, self.fillColor.CGColor);
CGContextEOFillPath(context);
CGPathRelease(path);
It works well in simulator, but on device it doesn't.
On device, outer circle is filled with color, and inner circle wasn't clipped.
How can I modify my code to work well on device?

I fixed it by using CGContextAddEllipseInRect method instead of CGContextAddArc.
WPCircleOverlay * circleOverlay = self.overlay;
CGRect rectForMapRect = [self rectForMapRect:mapRect];
CGPoint centerPoint = [self pointForMapPoint:MKMapPointForCoordinate(circleOverlay.coordinate)];
CGFloat innerRadius = MKMapPointsPerMeterAtLatitude(circleOverlay.coordinate.latitude) * circleOverlay.innerRadius;
CGFloat outerRadius = MKMapPointsPerMeterAtLatitude(circleOverlay.coordinate.latitude) * circleOverlay.outerRadius;
CGRect innerRect = CGRectMake(centerPoint.x - innerRadius, centerPoint.y - innerRadius, innerRadius * 2.0, innerRadius * 2.0);
CGRect outerRect = CGRectMake(centerPoint.x - outerRadius, centerPoint.y - outerRadius, outerRadius * 2.0, outerRadius * 2.0);
if (CGRectIntersectsRect(rectForMapRect, outerRect)) {
CGContextAddRect(context, rectForMapRect);
CGContextSaveGState(context);
CGContextClip(context);
CGContextAddEllipseInRect(context, outerRect);
CGContextAddEllipseInRect(context, innerRect);
CGContextSaveGState(context);
CGContextEOClip(context);
UIColor * color = [self.fillColor copy];
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, outerRect);
CGContextRestoreGState(context);
CGContextRestoreGState(context);
UIGraphicsPopContext();
}

Related

UIBezierpath setLineDash arc thick and thin [duplicate]

I have this line around my shape:
The problem is, it obviously goes from 1px thick, to 2px. Here's the code I'm using:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
CGContextSetFillColorWithColor(context, [BUTTON_COLOR CGColor]);
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(context, 1);
int radius = 8;
CGContextMoveToPoint(context, 0, self.frame.size.height / 2);
CGContextAddLineToPoint(context, POINT_WIDTH, self.frame.size.height);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width - radius,
rect.origin.y + rect.size.height);
CGContextAddArc(context, rect.origin.x + rect.size.width - radius,
rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + radius);
CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + radius,
radius, 0.0f, -M_PI / 2, 1);
CGContextAddLineToPoint(context, POINT_WIDTH, 0);
CGContextAddLineToPoint(context, 0, self.frame.size.height / 2);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
Any ideas?
The reason your top, bottom, and right edges appear thinner than your diagonals is because you are chopping off half of the lines drawn along those edges. As Costique mentioned, Core Graphics draws a stroke so that it is centered on the path. That means half of the stroke lies on one side of the path, and half of the stroke lies on the other side. Since your path runs exactly along the top, right, and bottom edges of your view, half of the stroke falls outside the bounds of your view and isn't drawn.
Costique is also correct that you should move your paths in by .5 points. (Technically you should move by half the stroke width.) However, you're not specifying your coordinates uniformly based on the rect variable, so moving all your paths is difficult.
What you want to do is inset rect by .5 in both dimensions, adjust your graphics context so that its origin is at rect.origin, and use only rect.size to get your coordinates - not self.frame.size. Here's my test:
- (void)drawRect:(CGRect)dirtyRect
{
CGContextRef gc = UIGraphicsGetCurrentContext();
CGContextSaveGState(gc); {
CGContextSetFillColorWithColor(gc, [UIColor colorWithWhite:.9 alpha:1].CGColor);
CGContextSetStrokeColorWithColor(gc, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(gc, 1);
static const CGFloat Radius = 8;
static const CGFloat PointWidth = 20;
CGRect rect = CGRectInset(self.bounds, .5, .5);
CGContextTranslateCTM(gc, rect.origin.x, rect.origin.x);
rect.origin = CGPointZero;
CGContextMoveToPoint(gc, 0, rect.size.height / 2);
CGContextAddLineToPoint(gc, PointWidth, rect.size.height);
CGContextAddLineToPoint(gc, rect.origin.x + rect.size.width - Radius,
rect.origin.y + rect.size.height);
CGContextAddArc(gc, rect.origin.x + rect.size.width - Radius,
rect.origin.y + rect.size.height - Radius, Radius, M_PI / 2, 0.0f, 1);
CGContextAddLineToPoint(gc, rect.origin.x + rect.size.width, rect.origin.y + Radius);
CGContextAddArc(gc, rect.origin.x + rect.size.width - Radius, rect.origin.y + Radius,
Radius, 0.0f, -M_PI / 2, 1);
CGContextAddLineToPoint(gc, PointWidth, 0);
CGContextAddLineToPoint(gc, 0, rect.size.height / 2);
CGContextClosePath(gc);
CGContextDrawPath(gc, kCGPathFillStroke);
} CGContextRestoreGState(gc);
}
Here's the result:
Core Graphics draws the stroke on the path edge, that is, half of the stroke width lies inside the path, and the other half, outside. Since you cannot draw outside of the view, half of the stroke is clipped by the context. You have to inset the path by half the stroke width.
The complete example:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
CGContextSetFillColorWithColor(context, [BUTTON_COLOR CGColor]);
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(context, 1);
int radius = CORNER_RADIUS;
CGRect pathRect = CGRectInset(self.bounds, 0.5f, 0.5f);
CGContextMoveToPoint(context, CGRectGetMinX(pathRect), CGRectGetMidY(pathRect));
CGContextAddLineToPoint(context, CGRectGetMinX(pathRect) + POINT_WIDTH, CGRectGetMaxY(pathRect));
CGContextAddArc(context, CGRectGetMaxX(pathRect) - radius,
CGRectGetMaxY(pathRect) - radius, radius, M_PI / 2, 0.0f, 1);
CGContextAddArc(context, CGRectGetMaxX(pathRect) - radius, CGRectGetMinY(pathRect) + radius,
radius, 0.0f, -M_PI / 2, 1);
CGContextAddLineToPoint(context, CGRectGetMinX(pathRect) + POINT_WIDTH, CGRectGetMinY(pathRect));
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
Note that several redundant drawing commands are removed without affecting the result. Enjoy.

Rounded Corner For CGRectAddArc

The below code draws the rounded corner at the at the end of the path(green color). But, not at the start. What could be done so that I have the rounded corner at the start too(where Red colour starts). Its not perfect round at the start.
-(void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat arcStartAngle = M_PI;
CGFloat arcEndAngle = 2 * M_PI;
CGPoint startPoint = CGPointMake(0,CGRectGetMidY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMaxX(rect),CGRectGetMidY(rect));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat colors[] =
{
1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0
};
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, NULL, 2);
CGColorSpaceRelease(colorSpace);
CGMutablePathRef arc = CGPathCreateMutable();
CGPathMoveToPoint(arc, NULL, startPoint.x, startPoint.y);
CGPoint arcCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
CGFloat radius = CGRectGetMidX(rect);
CGPathAddArc(arc, NULL, arcCenter.x, arcCenter.y, radius-5.0,
arcStartAngle, arcEndAngle, NO);
CGPathRef strokedArc = CGPathCreateCopyByStrokingPath(arc, NULL, 10.0f,
kCGLineCapRound, kCGLineJoinRound, 10.0f);
CGContextSaveGState(context);
CGContextAddPath(context, strokedArc);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGContextDrawPath(context, kCGPathFillStroke);
CGGradientRelease(gradient);
CGContextRestoreGState(context);
}
I had been working on your question and this is what I have found.
Your problem is related to your begin path point you have
CGPathMoveToPoint(arc, NULL, startPoint.x, startPoint.y);
but for your example you need start on startPoint.x + 5 because you are using 10 of width of your path so you need modify this line and use this instead
CGPathMoveToPoint(arc, NULL, startPoint.x + 5, startPoint.y);
and now should work, This is the result
I hope this helps you.

How do I get the last point of CGContext in Objective-C?

I'm working on a custom progress bar and I want to get the last point of CGContext because I want to add an image to current state.
This is my code:
-(void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGPoint center = CGPointMake(rect.size.width/2, rect.size.height/2);
float minSize = MIN(rect.size.width, rect.size.height);
float lineWidth = _strokeWidth;
if(lineWidth == -1.0) lineWidth = minSize*_strokeWidthRatio;
float radius = (minSize-lineWidth)/2;
float endAngle = M_PI*(self.value*2);
//what should i do here
//_pont.center = CGPointMake
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, center.x, center.y);
CGContextRotateCTM(ctx, -M_PI*0.5);
CGContextSetLineWidth(ctx, lineWidth);
CGContextSetLineCap(ctx, kCGLineCapRound);
// "Full" Background Circle:
CGContextBeginPath(ctx);
CGContextAddArc(ctx, 0, 0, radius, 0, 2*M_PI, 0);
CGContextSetStrokeColorWithColor(ctx, [_color colorWithAlphaComponent:0.1].CGColor);
CGContextStrokePath(ctx);
// Progress Arc:
CGContextBeginPath(ctx);
CGContextAddArc(ctx, 0, 0, radius, 0, endAngle, 0);
CGContextSetStrokeColorWithColor(ctx, [_color colorWithAlphaComponent:0.9].CGColor);
CGContextStrokePath(ctx);
CGContextRestoreGState(ctx);
}
You have to use CGContextGetPathCurrentPoint, but you have to call it before you clear the path by calling stroke or fill. So look at the following playground and see what the value returned by CGContextGetPathCurrentPoint return varies by when you call it.
import CoreGraphics
import UIKit
let lineWidth = CGFloat(10)
let rect = CGRectMake(0, 0, 100, 100)
let size = CGSizeMake(100, 100)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let ctx = UIGraphicsGetCurrentContext()
let center = CGPointMake(rect.size.width/2, rect.size.height/2);
let minSize = fmin(rect.size.width, rect.size.height);
let radius = (minSize-10)/2;
let endAngle = CGFloat(M_PI*(20.0*2))
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, center.x, center.y);
CGContextRotateCTM(ctx, CGFloat(-M_PI*0.5));
CGContextSetLineWidth(ctx, lineWidth);
CGContextSetLineCap(ctx, CGLineCap.Round);
// "Full" Background Circle:
CGContextBeginPath(ctx);
CGContextAddArc(ctx, 0, 0, radius, 0, CGFloat(2*M_PI), 0);
CGContextSetStrokeColorWithColor(ctx, UIColor.greenColor().CGColor);
var endPoint = CGContextGetPathCurrentPoint(ctx)
CGContextStrokePath(ctx);
endPoint = CGContextGetPathCurrentPoint(ctx)
// Progress Arc:
CGContextBeginPath(ctx);
CGContextAddArc(ctx, 0, 0, radius, 0, endAngle, 0);
endPoint = CGContextGetPathCurrentPoint(ctx)
CGContextSetStrokeColorWithColor(ctx, UIColor.redColor().CGColor);
CGContextStrokePath(ctx);
CGContextRestoreGState(ctx);
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
It's all simple geometry.
With context transformation matrix from your drawRect: code, you should use:
CGPoint lastPoint = CGPointMake(radius * cos(endAngle), radius * sin(endAngle));
After reset or before setup CTM, you should use:
//Replace "what should i do here" comment with following code:
CGPoint lastPoint = CGPointZero;
lastPoint.x = center.x + radius * cos(endAngle - M_PI * 0.5);
lastPoint.y = center.y + radius * sin(endAngle - M_PI * 0.5);
Also, second solution will work outside of drawRect: implementation.

How to draw a triangle over a UIImage in a UIImageView

I'm having trouble drawing atop an image in a UIImageView, I've already looked at "Draw another image on a UIImage" But it was only so much help. Here's my scenario: I have a UIPopoverController with a UITableView in it and I want to display a triangle pointing up when the user is at the bottom of scrollable table view.
My code:
- (UIImage *) drawTriangleInViewForSize:(CGSize)sizeOfView
imageToDrawOn:(UIImage *)underImage
isAtTop:(BOOL)top{
CGPoint firstPoint;
CGPoint secondPoint;
CGPoint thirdPoint;
if(!top){
//I want to draw a equilateral triangle (side length 10) in the center of the image in
//the imageView, these are the coordinates I am using.
firstPoint = CGPointMake(underImage.size.width * 0.5 + 5, underImage.size.height * 0.5 - 5);
secondPoint = CGPointMake((firstPoint.x - 10), firstPoint.y);
thirdPoint = CGPointMake(underImage.size.width * 0.5,
underImage.size.width * 0.5 + 5);
}
*/
**DISREGARD**
else{
firstPoint = CGPointMake(sizeOfView.width * 0.5 + 5,
self.tableViewChoices.rowHeight * 0.5 - 5);
secondPoint = CGPointMake((firstPoint.x - 10), firstPoint.y);
thirdPoint = CGPointMake(sizeOfView.width * 0.5,
self.tableViewChoices.rowHeight * 0.5 + 5);
}*/
//get the size of the image for the drawInRect: method
CGFloat imageViewWidth = sizeOfView.width;
CGFloat imageViewHeight = self.tableViewChoices.rowHeight;
//set the graphics context to be the size of the image
UIGraphicsBeginImageContextWithOptions(underImage.size, YES, 0.0);
[underImage drawInRect:CGRectMake(0.0, 0.0, imageViewWidth, imageViewHeight)];
//set the line attributes
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextSetLineWidth(ctx, 0.05);
UIGraphicsPushContext(ctx);
//draw the triangle
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, firstPoint.x, firstPoint.y);
CGContextAddLineToPoint(ctx, secondPoint.x, secondPoint.y);
CGContextAddLineToPoint(ctx, thirdPoint.x, thirdPoint.y);
CGContextAddLineToPoint(ctx, firstPoint.x, firstPoint.y);
CGContextClosePath(ctx);
UIGraphicsPopContext();
//get the image
UIImage *rslt = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return rslt;
}
I can't quite seem to draw to the triangle to the UIImage in the imageView, the end result is either a completely black ImageView (the UIImage is just a white.png), or one line at the top of the imageview (depending on whether I use drawInRect: or drawAtPoint: and what coordinates I use). All I need to do is draw a triangle pointing up, not a hard thing, and it really looks like I'm doing it "by the book."
Here is my completed code to draw a triangle onto a UIImage in a UIImageView.
//draws triangle up or down on a UIImage.
+(UIImage *) drawTriangleInViewForSize:(CGSize)sizeOfView
imageToDrawOn:(UIImage *)underImage
isAtTop:(BOOL)top
{
CGFloat rowHeight = underImage.size.height; //44; //self.tableViewChoices.rowHeight;
CGPoint firstPoint;
CGPoint secondPoint;
CGPoint thirdPoint;
CGFloat imageViewWidth = sizeOfView.width;
CGFloat imageViewHeight = rowHeight;
if(!top){
//draw a upward facing triangle in the center of the view.
firstPoint = CGPointMake(imageViewWidth * 0.5 + 15, imageViewHeight * 0.5 - 5);
secondPoint = CGPointMake((firstPoint.x - 30), firstPoint.y);
thirdPoint = CGPointMake(imageViewWidth * 0.5,
imageViewHeight * 0.5 + 5);
}else{
//disregard this 'else'
firstPoint = CGPointMake(sizeOfView.width * 0.5 + 15,
rowHeight * 0.5 - 5);
secondPoint = CGPointMake((firstPoint.x - 10), firstPoint.y);
thirdPoint = CGPointMake(sizeOfView.width * 0.5,
rowHeight * 0.5 + 5);
}
//get the image context with options(recommended funct to use)
//get the size of the imageView
UIGraphicsBeginImageContextWithOptions(CGSizeMake(imageViewWidth, imageViewHeight), YES, 0.0);
//use the the image that is going to be drawn on as the receiver
[underImage drawInRect:CGRectMake(0.0, 0.0, imageViewWidth, imageViewHeight)];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 0.5);
//UIGraphicsPushContext(ctx);
//uses path ref
CGMutablePathRef path = CGPathCreateMutable();
//draw the triangle
CGPathMoveToPoint(path, NULL, firstPoint.x, firstPoint.y);
CGPathAddLineToPoint(path, NULL, secondPoint.x, secondPoint.y);
CGPathAddLineToPoint(path, NULL, thirdPoint.x, thirdPoint.y);
CGPathAddLineToPoint(path, NULL, firstPoint.x, firstPoint.y);
//close the path
CGPathCloseSubpath(path);
//add the path to the context
CGContextAddPath(ctx, path);
CGContextSetFillColorWithColor(ctx, [UIColor whiteColor].CGColor);
CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextFillPath(ctx);
CGContextAddPath(ctx, path);
CGContextStrokePath(ctx);
CGPathRelease(path);
//UIGraphicsPopContext();
//get the new image with the triangle
UIImage *rslt = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return rslt;
}
Your code looks too complicated for that kind of task. Why don't you use simple UIImageView with any triangle image you ever want? Just show it when the table at the bottom and that's all.
Note, that when you're implementing delegate for UITableView you're also able to catch it's UIScrollView's delegate messages such as scrollView:didEndDecelerating: and so on.
Regarding your code you've missed an actual path draw:
CGContextAddPath(context, trianglePath);
CGContextFillPath(context);
To make it possible you should use path-related functions. Using CGContext-related functions seems to be improper since such approach may remove old path. So the better way is to create your own mutable path in the context, draw your triangle in it and then draw this path into context by functions mentioned above.

How can I make crisper images using quartz?

I'm trying to make high-resolution graphics dynamically. Unfortunately when I make arcs they look really fuzzy.
Here's my code
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect bounds = [self bounds];
CGPoint center = CGPointMake(bounds.size.width / 2.0, bounds.size.height / 2.0);
CGFloat lineWidth = 30.0;
CGFloat innerRadius = (bounds.size.width / 2.0) - lineWidth;
CGFloat outerRadius = innerRadius + lineWidth;
CGFloat startAngle = -((float)M_PI / 2.0);
CGFloat endAngle = ((self.percent / 100.0) * 2 * (float)M_PI) + startAngle;
UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath];
processBackgroundPath.lineWidth = lineWidth;
CGFloat radius = (self.bounds.size.width - lineWidth) / 2.0;
CGFloat fullAngle = (2.0 * (float)M_PI) + startAngle;
[processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:fullAngle clockwise:YES];
[[UIColor whiteColor] set];
[processBackgroundPath stroke];
CGMutablePathRef progressPath = CGPathCreateMutable();
CGPathMoveToPoint(progressPath, NULL, center.x, center.y - innerRadius);
CGPathAddArc(progressPath, NULL, center.x, center.y, innerRadius, startAngle, endAngle, YES);
CGPathAddArc(progressPath, NULL, center.x, center.y, outerRadius, endAngle, startAngle, NO);
CGPathCloseSubpath(progressPath);
UIColor *aColor = [UIColor colorWithRed:0.941 green:0.776 blue:0.216 alpha:1.0];
[aColor setFill];
CGContextAddPath(context, progressPath);
CGContextFillPath(context);
CGPathRelease(progressPath);
}
What do I need to do to make the donut more crisp?

Resources