I am trying to draw a straight line between two points in overlay view.
In MKOverlayView method, I think I am doing correctly but I don't understand why it's not drawing any lines...
Does anyone know why?
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
{
UIGraphicsPushContext(context);
MKMapRect theMapRect = [[self overlay] boundingMapRect];
CGRect theRect = [self rectForMapRect:theMapRect];
// Clip the context to the bounding rectangle.
CGContextAddRect(context, theRect);
CGContextClip(context);
CGPoint startP = {theMapRect.origin.x, theMapRect.origin.y};
CGPoint endP = {theMapRect.origin.x + theMapRect.size.width,
theMapRect.origin.y + theMapRect.size.height};
CGContextSetLineWidth(context, 3.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextBeginPath(context);
CGContextMoveToPoint(context, startP.x, startP.y);
CGContextAddLineToPoint(context, endP.x, endP.y);
CGContextStrokePath(context);
UIGraphicsPopContext();
}
Thank you for your help.
The line is being drawn using startP and endP which are CGPoint values but they are initialized using theMapRect which contains MKMapPoint values.
Instead, initialize them using theRect which you are converting from theMapRect using rectForMapRect.
Also, for the line width, you may want to scale it using the MKRoadWidthAtZoomScale function. Otherwise, a fixed line width of 3.0 will not be visible unless you are zoomed in very close.
The changed code would look like this:
CGPoint startP = {theRect.origin.x, theRect.origin.y};
CGPoint endP = {theRect.origin.x + theRect.size.width,
theRect.origin.y + theRect.size.height};
CGContextSetLineWidth(context, 3.0 * MKRoadWidthAtZoomScale(zoomScale));
Finally, instead of a custom MKOverlayView, why not use a MKPolylineView to avoid drawing lines manually?
Related
How to draw a line on the UIImageView? I am using the UIImageView Sub Class, Its not support drawRect: method. I want to draw a line on the image view. How can I do this? Please help me. I am using below code for draw line.
- (void)drawRect:(CGRect)rect {
CGContextRef c = UIGraphicsGetCurrentContext();
CGFloat red[4] = {1.0f, 0.0f, 0.0f, 1.0f};
CGContextSetStrokeColor(c, red);
CGContextBeginPath(c);
CGContextMoveToPoint(c, 5.0f, 5.0f);
CGContextAddLineToPoint(c, 50.0f, 50.0f);
CGContextStrokePath(c);
}
The following code works by creating a new image the same size as the original, drawing a copy of the original image onto the new image, then drawing a 1 pixel line along to the top of the new image.
// UIImage *originalImage = <the image you want to add a line to>
// UIColor *lineColor = <the color of the line>
UIGraphicsBeginImageContext(originalImage.size);
// Pass 1: Draw the original image as the background
[originalImage drawAtPoint:CGPointMake(0,0)];
// Pass 2: Draw the line on top of original image
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 1.0);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, originalImage.size.width, 0);
CGContextSetStrokeColorWithColor(context, [lineColor CGColor]);
CGContextStrokePath(context);
// Create new image
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
// Tidy up
UIGraphicsEndImageContext();
First, I wouldn't use a UIImageView. In fact the docs say...
If your subclass needs custom drawing code, it is recommended you use
UIView as the base class.
Use a UIView.
In the UIView add a UIImageView subView and put the image in there. Now you can do you custom drawing in the drawRect method of the UIView and it will appear on top of the image.
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 5.0);
CGContextStrokeRect(context, self.bounds);
}
If this isn't working then the drawRect method probably isn't being called. Set a breakpoint to test it.
The best way to go about it in my opinion would be to take a invisible UIView of the same size as UIImageView for drawing and implement the touch methods on the UIView for drawing.
you can use something like:
CGPoint temp=[touch locationInView:self];
Use this in methods like:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
Store all the points in an array and then draw those points on the UIImageView as it is.
Refer to Muhammad Saad Ansari
With Swift
func drawLines ( originalImage:UIImage, lineColor:CGColor ) -> UIImage{
UIGraphicsBeginImageContextWithOptions(originalImage.size,false,0.0)
originalImage.drawInRect(CGRectMake( 0, 0, originalImage.size.width, originalImage.size.height ))
let context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 5.0);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, originalImage.size.width, 0);
CGContextSetStrokeColorWithColor(context,lineColor);
CGContextStrokePath(context);
// Create new image
var newImage = UIGraphicsGetImageFromCurrentImageContext();
// Tidy up
UIGraphicsEndImageContext();
return newImage
}
Should I be able to override drawInContext() and draw outside the bounds of my CALayer? Even though my layer has maskToBounds set to NO (the default) my drawInContext() is called with a clip set to the bounds of my layer and I am unable to draw outside of it.
My test layer does something like this:
-(void)drawInContext:(CGContextRef)context
{
[super drawInContext:context];
NSLog(#"mask to bounds=%d", self.masksToBounds); // NO
CGRect clip = CGContextGetClipBoundingBox(context);
NSLog(#"clip=%f,%f,%f,%f", clip.origin.x, clip.origin.y, clip.size.width, clip.size.height); // reports the bounds
CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGContextBeginPath(context);
CGContextSetLineWidth(context, 5.0);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, 500, 0.0); // wider than my layer...
CGContextStrokePath(context);
}
and here is how I set it up:
- (void)viewDidLoad
{
[super viewDidLoad];
CALayer *layer = [[MyLayer alloc] init];
layer.needsDisplayOnBoundsChange=YES;
layer.frame = CGRectMake(50, 50, 200, 200);
[self.view.layer addSublayer:layer];
}
Is this just a limitation of core animation layers? (Do I need to draw in the layer above this layer?)
thanks.
There is no way for a layer's contents to overflow its bounds; however, you can add a sublayer that overflows by specifying a frame that stretches outside the bounds and setting the masksToBounds property to NO (the default).
For an iPhone application I want to draw a circle, that is only for an x percentage filled.
Something like this:
I have no problems calculating the radius, the degrees or the radians, that is no problem. Also drawing the circle is already done. But how do I get the iPhone SDK to draw the part that is filled.
I can draw a rectangle that size, but not part of a circle.
I just want to draw that on a a normal context.
Hope someone can give me any pointers here.
A lot of people have showed you how this can be done in Core Graphics but it can also be done with Core Animation which gives the big addition of easily being able to animate the percentage of the pie shape.
The following code will create both the ring and the partly filled layers (even though you said that you already can draw the ring) since its nice to have both the ring and the pie shape to be drawn using the same method.
If you animate the strokeStart or strokeEnd properties of the pieShape layer you will have the percentage animate. As with all Core Animation code you will need to add QuartzCore.framework to your project and include <QuartzCore/QuartzCore.h> in your code.
// Create a white ring that fills the entire frame and is 2 points wide.
// Its frame is inset 1 point to fit for the 2 point stroke width
CGFloat radius = MIN(self.frame.size.width,self.frame.size.height)/2;
CGFloat inset = 1;
CAShapeLayer *ring = [CAShapeLayer layer];
ring.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
cornerRadius:radius-inset].CGPath;
ring.fillColor = [UIColor clearColor].CGColor;
ring.strokeColor = [UIColor whiteColor].CGColor;
ring.lineWidth = 2;
// Create a white pie-chart-like shape inside the white ring (above).
// The outside of the shape should be inside the ring, therefore the
// frame needs to be inset radius/2 (for its outside to be on
// the outside of the ring) + 2 (to be 2 points in).
CAShapeLayer *pieShape = [CAShapeLayer layer];
inset = radius/2 + 2; // The inset is updated here
pieShape.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
cornerRadius:radius-inset].CGPath;
pieShape.fillColor = [UIColor clearColor].CGColor;
pieShape.strokeColor = [UIColor whiteColor].CGColor;
pieShape.lineWidth = (radius-inset)*2;
// Add sublayers
// NOTE: the following code is used in a UIView subclass (thus self is a view)
// If you instead chose to use this code in a view controller you should instead
// use self.view.layer to access the view of your view controller.
[self.layer addSublayer:ring];
[self.layer addSublayer:pieShape];
Use CGContext's arc functions:
CGContextAddArc(context,
centerX,
centerY,
radius,
startAngleRadians,
endAngleRadians,
clockwise ? 1 : 0);
See the documentation for CGContextAddArc().
Try this:
CGContextMoveToPoint(the center point)
CGContextAddLineToPoint(the starting point of the fill path on the circumference)
CGContextAddArcToPoint(the ending point of the fill path on the circumference)
CGContextAddLineToPoint(the center point)
CGContextFillPath
I implemented a pie progress view that looks similar to what you are doing. It's open source. Hopefully the source code will help.
SSPieProgressView.h source
SSPieProgressView.m source
CircleViewController.h
#import <UIKit/UIKit.h>
#interface CircleViewController : UIViewController
#end
CircleViewController.m
#import "CircleViewController.h"
#import "GraphView.h"
#interface CircleViewController ()
#end
#implementation CircleViewController
- (void)viewDidLoad {
[super viewDidLoad];
GraphView *graphView = [[GraphView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
graphView.backgroundColor = [UIColor whiteColor];
graphView.layer.borderColor = [UIColor redColor].CGColor;
graphView.layer.borderWidth = 1.0f;
[self.view addSubview:graphView];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
GraphView.h
#import <UIKit/UIKit.h>
#interface GraphView : UIView
#end
GraphView.m
#import "GraphView.h"
#implementation GraphView
- (void)drawRect:(CGRect)rect {
CGPoint circleCenter = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
[self drawCircleWithCircleCenter:(CGPoint) circleCenter radius:80 firstColor:[UIColor blueColor].CGColor secondeColor:[UIColor redColor].CGColor lineWidth:2 startDegree:0 currentDegree:90];
//[self drawCircleWithCircleCenter2:(CGPoint) circleCenter radius:80 firstColor:[UIColor blueColor].CGColor secondeColor:[UIColor redColor].CGColor lineWidth:2 startDegree:0 currentDegree:90];
}
- (void)drawCircleWithCircleCenter:(CGPoint) circleCenter
radius:(CGFloat)radius
firstColor:(CGColorRef)firstColor
secondeColor:(CGColorRef)secondeColor
lineWidth:(CGFloat)lineWidth
startDegree:(float)startDegree
currentDegree:(float)endDegree {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, lineWidth);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x , circleCenter.y, radius, [self radians:startDegree], [self radians:endDegree], 0);
CGContextSetFillColorWithColor(context, firstColor);
CGContextFillPath(context);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x, circleCenter.y, radius, [self radians:endDegree], [self radians:startDegree], 0);
CGContextSetFillColorWithColor(context, secondeColor);
CGContextFillPath(context);
}
- (void)drawCircleWithCircleCenter2:(CGPoint) circleCenter
radius:(CGFloat)radius
firstColor:(CGColorRef)firstColor
secondeColor:(CGColorRef)secondeColor
lineWidth:(CGFloat)lineWidth
startDegree:(float)startDegree
currentDegree:(float)endDegree {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, lineWidth);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x , circleCenter.y, radius, [self radians:startDegree], [self radians:endDegree], 0);
CGContextSetFillColorWithColor(context, firstColor);
CGContextFillPath(context);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x, circleCenter.y, radius, [self radians:endDegree], [self radians:startDegree], 0);
CGContextSetStrokeColorWithColor(context, secondeColor);
CGContextStrokePath(context);
}
-(float) radians:(double) degrees {
return degrees * M_PI / 180;
}
#end
note: you can use one of the 2 methods:
"drawCircleWithCircleCenter" or "drawCircleWithCircleCenter2"
this code if you want to split cell on 2 parts only
if you want to split cell on more than 2 parts you can check this : "Drawing a circle ,filled different parts with different color" and check the answer start with this Phrase "we have 6 class"
Well, since nobody used NSBezierPath so far, I figured I could provide the solution I recently used for the same problem:
-(void)drawRect:(NSRect)dirtyRect
{
double start = -10.0; //degrees
double end = 190.0; //degrees
NSPoint center = NSMakePoint(350, 200);
double radius = 50;
NSBezierPath *sector = [NSBezierPath bezierPath];
[sector moveToPoint:center];
[sector appendBezierPathWithArcWithCenter:center radius:radius startAngle:start endAngle:end];
[sector lineToPoint:center];
[sector fill];
}
Below is a full method I am using that does this with Core Graphics, adapting and expanding on mharper's comment above.
This code is for OSX Cocoa, but could easily be changed to iOS, by modifying how you get the context.
- (void)drawPieShapedCircleWithRadius:(CGFloat)radius
strokeColor:(CGColorRef)strokeColor
fillColor:(CGColorRef)fillColor
lineWidth:(CGFloat)lineWidth
currentDegrees:(float)currentDegrees
startDegrees:(float)startDegrees {
// get the context
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
// Set the color of the circle stroke and fill
CGContextSetStrokeColorWithColor(context, strokeColor);
CGContextSetFillColorWithColor(context, fillColor);
// Set the line width of the circle
CGContextSetLineWidth(context, 1);
// Calculate the middle of the circle
CGPoint circleCenter = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
// Move the bezier to the center of the circle
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y); // move to the center point
// Draw the arc from the start point (hardcoded as the bottom of the circle) to the center
CGContextAddLineToPoint(context, circleCenter.x, circleCenter.y + radius);
// Draw the arc around the circle from the start degrees point to the current degrees point
CGContextAddArc(context, circleCenter.x , circleCenter.y, radius, [self radians:startDegrees], [self radians:startDegrees + currentDegrees], 0);
// Draw the line back into the center of the circle
CGContextAddLineToPoint(context, circleCenter.x, circleCenter.y);
// Fill the circle
CGContextFillPath(context);
// Draw the line around the circle
CGContextStrokePath(context);
}
Try this code in a UIView, Example "MyChartClass"...
- (void)drawRect:(CGRect)rect {
int c=(int)[itemArray count];
CGFloat angleArray[c];
CGFloat offset;
int sum=0;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(context, false);
CGContextSetShouldAntialias(context, false);
for(int i=0;i<[itemArray count];i++) {
sum+=[[itemArray objectAtIndex:i] intValue];
}
for(int i=0;i<[itemArray count];i++) {
angleArray[i]=(float)(([[itemArray objectAtIndex:i] intValue])/(float)sum)*(2*3.14);
CGContextMoveToPoint(context, radius, radius);
if(i==0)
CGContextAddArc(context, radius, radius, radius, 0,angleArray[i], 0);
else
CGContextAddArc(context, radius, radius, radius,offset,offset+angleArray[i], 0);
offset+=angleArray[i];
CGContextSetFillColorWithColor(context, ((UIColor *)[myColorArray objectAtIndex:i]).CGColor);
CGContextClosePath(context);
CGContextFillPath(context);
}
}
Implementation in your UIViewController
MyChartClass *myChartClass=[[MyChartClass alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
myChartClass.backgroundColor = [UIColor clearColor];
myChartClass.itemArray=[[NSArray alloc]initWithObjects:#"75",#"25", nil];
myChartClass.myColorArray=[[NSArray alloc]initWithObjects:[UIColor blackColor],[UIColor whiteColor], nil];
myChartClass.radius=100;
[self.view addSubview:myChartClass];
Regards.
I try to draw some lines with different colors.
This code tries to draw two rectangles with 1px thin lines. However, the second rectangle is drawn with 2px width lines, while the first one is drawn with 1px width.
- (void)addLineFrom:(CGPoint)p1 to:(CGPoint)p2 context:(CGContextRef)context {
// set the current point
CGContextMoveToPoint(context, p1.x, p1.y);
// add a line from the current point to the wanted point
CGContextAddLineToPoint(context, p2.x, p2.y);
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint from, to;
// ----- draw outer black frame (left, bottom, right) -----
CGContextBeginPath(context);
// set the color
CGFloat lineColor[4] = {26.0f * colorFactor, 26.0f * colorFactor, 26.0f * colorFactor, 1.0f};
CGContextSetStrokeColor(context, lineColor);
// left
from = CGPointZero;
to = CGPointMake(0.0f, rect.size.height);
[self addLineFrom:from to:to context:context];
// bottom
from = to;
to = CGPointMake(rect.size.width, rect.size.height);
[self addLineFrom:from to:to context:context];
// right
from = to;
to = CGPointMake(rect.size.width, 0.0f);
[self addLineFrom:from to:to context:context];
CGContextStrokePath(context);
CGContextClosePath(context);
// ----- draw the middle light gray frame (left, bottom, right) -----
CGContextSetLineWidth(context, 1.0f);
CGContextBeginPath(context);
// set the color
CGFloat lineColor2[4] = {94.0f * colorFactor, 94.0f * colorFactor, 95.0f * colorFactor, 1.0f};
CGContextSetStrokeColor(context, lineColor2);
// left
from = CGPointMake(200.0f, 1.0f);
to = CGPointMake(200.0f, rect.size.height - 2.0f);
[self addLineFrom:from to:to context:context];
// bottom
from = to;
to = CGPointMake(rect.size.width - 2.0f, rect.size.height - 2.0f);
[self addLineFrom:from to:to context:context];
// right
from = to;
to = CGPointMake(rect.size.width - 2.0f, 1.0f);
[self addLineFrom:from to:to context:context];
// top
from = to;
to = CGPointMake(1.0f, 1.0f);
[self addLineFrom:from to:to context:context];
CGContextStrokePath(context);
}
If memory serves, antialiasing is on by default which may cause more pixels to be affected by your drawing than you intend. Turn antialiasing off for your CGContextRef and see if that helps.
iOS:
CGContextRef context = UIGraphicsGetCurrentContext();
[context setShouldAntialias:NO];
Mac:
CGContextRef context = [NSGraphicsContext currentContext];
[context setShouldAntialias:NO];
The first one appears to be one pixel thick because you've drawn it around the perimeter of the dirty rect, which UIView clipped to for you, making it an inner stroke. But, in fact, both rectangles have the same problem.
On another question, I wrote a full description of the real problem and the real solutions. Turning off AA will suffice for straight lines, but you'll hate it as soon as you draw rotated or draw a diagonal.
In my application i can draw a line in a UIImageView by the code below,and i want redraw the line even longer when i call the function,however,the output come out is not as expected,it will just draw a new line and remove the old one,the length is remain the same jus the y position different,i dont know which line of my code is wrong or i havent understand the CGContext class in correct way,please help i have scratch my head all the days and cannot find out the problem
- (void) drawLine {
lastPoint = currentPoint;
lastPoint.y -= 40;
//drawImage is a UIImageView declared at header
UIGraphicsBeginImageContext(drawImage.frame.size);
[drawImage.image drawInRect:CGRectMake(0, 0, drawImage.frame.size.width, drawImage.frame.size.height)];
//sets the style for the endpoints of lines drawn in a graphics context
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineCap(ctx, kCGLineCapButt);
//sets the line width for a graphic context
CGContextSetLineWidth(ctx,2.0);
//set the line colour
CGContextSetRGBStrokeColor(ctx, 1.0, 0.0, 0.0, 1.0);
//creates a new empty path in a graphics context
CGContextBeginPath(ctx);
//begin a new path at the point you specify
CGContextMoveToPoint(ctx, currentPoint.x, currentPoint.y);
//Appends a straight line segment from the current point to the provided point
CGContextAddLineToPoint(ctx, currentPoint.x,lastPoint.y);
//paints a line along the current path
CGContextStrokePath(ctx);
drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
currentPoint = lastPoint;
}
I am sure you are done with this, but you have a typo.
CGContextAddLineToPoint(ctx, currentPoint.x,lastPoint.y);
should be:
CGContextAddLineToPoint(ctx, lastPoint.x,lastPoint.y);