I am trying to draw a square in my context, but it results in a rectangle with a short and longer side. (in my ipad retina simulator)
This is part of the drawRect method of my view object:
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat minx = 0;
CGFloat maxx = 45 - 1;
CGFloat miny = 0;
CGFloat maxy = 45 - 1;
CGContextSetLineWidth(context, 1.0);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextMoveToPoint(context, minx, maxy);
CGContextAddLineToPoint(context, minx, miny);
CGContextAddLineToPoint(context, maxx, miny);
CGContextStrokePath(context);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextMoveToPoint(context, maxx, miny);
CGContextAddLineToPoint(context, maxx, maxy);
CGContextAddLineToPoint(context, minx, maxy);
CGContextStrokePath(context);
What am I doing wrong? Do I need to set an aspect ratio or something?
Edit:
I found out that
CGContextScaleCTM(context, 1, 0.75f);
makes the result square again, but the end result is not spot on.
Does anybody know why I need to correct the vertical scale?
Found it....
Turns out I was modifying the views rect elsewhere.
Disabled that, and now my squares are square again.
Thanks for the hint Ismael !!
Related
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.
I having the follow pop up and I want to make the edges curved.
Using this code.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [[UIColor greenColor] CGColor]);
CGFloat baseWidth = 15.0;
CGFloat height = 10.0;
CGContextMoveToPoint(context, self.bounds.size.width / 2.0 - baseWidth / 2.0, height);
CGContextAddLineToPoint(context, self.bounds.size.width / 2.0, 0);
CGContextAddLineToPoint(context, self.bounds.size.width / 2.0 + baseWidth / 2.0, height);
CGContextAddLineToPoint(context, self.bounds.size.width, height);
CGContextAddLineToPoint(context, self.bounds.size.width, self.bounds.size.height);
CGContextAddLineToPoint(context, 0, self.bounds.size.height);
CGContextAddLineToPoint(context, 0, height);
CGContextClosePath(context);
CGContextStrokePath(context);
How can I curve the edges?
You are using Core Graphics and drawing those straight lines right up to each other in the screen shot above. To be able to draw curves you need to add in some calls to CGContectAddArcToPoint, there is also CGContextAddCurveToPoint but that is overkill for your implementation here.
If you want more information about Core Graphics then you can find a bunch of documentation here: https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_paths/dq_paths.html#//apple_ref/doc/uid/TP30001066-CH211-TPXREF101
With core graphics, is it possible to stroke the inside of a path? As opposed to the line weight being drawn half on the outside and half on the inside of a stroked path?
The reason being it would be easier to control the visible thickness of the stroke if say part of a view is on the screen edge and part is not. The part on the screen edge gets cut off, while the view edge that is fully on screen looks thicker (as both sides if the stroke are visible).
Clip to the path before you stroke it.
This draws no stroke:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor darkGrayColor].CGColor);
CGContextSetLineWidth(context, 14);
CGRect rrect = CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect), CGRectGetHeight(rect));
CGFloat radius = 30;
CGFloat minx = CGRectGetMinX(rrect), midx = CGRectGetMidX(rrect), maxx = CGRectGetMaxX(rrect);
CGFloat miny = CGRectGetMinY(rrect), midy = CGRectGetMidY(rrect), maxy = CGRectGetMaxY(rrect);
CGContextMoveToPoint(context, minx, midy);
CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
CGContextClosePath(context);
CGContextClip(context);
CGContextDrawPath(context, kCGPathStroke);
}
EDIT: This is what worked (according to the correct answer):
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor darkGrayColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextSetLineWidth(context, 14);
CGRect pathRect = CGRectMake(10, 10, rect.size.width -20, rect.size.height -20);
CGPathRef path = [UIBezierPath bezierPathWithRoundedRect:pathRect cornerRadius:20].CGPath;
CGContextAddPath(context, path);
CGContextClip(context);
CGContextAddPath(context, path);
CGContextDrawPath(context, kCGPathEOFillStroke);
}
I made of subclass of UIView called Bubble using which i want to draw a solid circle.Here is the drawRect: rect method
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGContextRef ctx = UIGraphicsGetCurrentContext();
// Drawing with a white stroke color
CGContextSetRGBStrokeColor(ctx, 1.0, 1.0, 1.0, 1.0);
// And draw with a blue fill color
CGContextSetRGBFillColor(ctx, 0.0, 0.0, 1.0, 1.0);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(ctx, 2.0);
CGRect rrect = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, self.bounds.size.height);
CGFloat radius = 30.0;
CGFloat minx = CGRectGetMinX(rrect), midx = CGRectGetMidX(rrect), maxx = CGRectGetMaxX(rrect);
CGFloat miny = CGRectGetMinY(rrect), midy = CGRectGetMidY(rrect), maxy = CGRectGetMaxY(rrect);
// Start at 1
CGContextMoveToPoint(ctx, minx, midy);
// Add an arc through 2 to 3
CGContextAddArcToPoint(ctx, minx, miny, midx, miny, radius);
// Add an arc through 4 to 5
CGContextAddArcToPoint(ctx, maxx, miny, maxx, midy, radius);
// Add an arc through 6 to 7
CGContextAddArcToPoint(ctx, maxx, maxy, midx, maxy, radius);
// Add an arc through 8 to 9
CGContextAddArcToPoint(ctx, minx, maxy, minx, midy, radius);
// Close the path
CGContextClosePath(ctx);
// Fill & stroke the path
CGContextDrawPath(ctx, kCGPathFillStroke);
}
I have taken the code from the apple sample Quartzdemo.
My problem is i am drawing circle inside a rectangle,but still i see the corners of the rectangle drawn.
How to clip them so that i see only circle? please help. i am new to quartz 2d
Here is how i call it in my viewDidLoad of my controller
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.5];
Bubble *bub = [[Bubble alloc]initWithFrame:CGRectMake(210.0, 90.0, 60.0, 60.0)];
[self.view addSubview:bub];
[bub release];
}
Put this is viewDidLoad
bub.backgroundColor = [UIColor clearColor];
I'm trying to make a more sophisticated drawing of a UILabels (or a UIView, putting the UILabel on top, should that be required) background. Since I want this to resize automatically when the user changes iPhone orientation, I am trying to implement this in a subclasses drawRect function. I would love to be able to tune this all in code, omitting the need for pattern images, if it's possible.
I got some hints from some former posts on the subject:
Gradients on UIView and UILabels On iPhone
and
Make Background of UIView a Gradient Without Sub Classing
Unfortunately they both miss the target, since I wanted to combine gradient with custom drawing. I want the CGGradient to be clipped by the line frame I am drawing (visible at the rounded corners) but I can't accomplish this.
The alternative would be to use the CAGradientLayer, which seem to work quite good, but that would require some resising of the frame at rotation, and the gradient layer seem to draw on top of the text, no matter how I try.
My question therefor is twofold
How do I modify the code below to make the gradient clip to the drawn "frame"
How do I make CAGradientLayer draw behind the text of the UILabel (or do I have to put a UIView behind the UILabel with the CAGradientLayer as background)
Here is my drawrect function:
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(c, [[UIColor clearColor] CGColor]);
CGContextSetStrokeColorWithColor(c, [strokeColor CGColor]);
CGContextSetLineWidth(c, 1);
CGFloat minx = CGRectGetMinX(rect), midx = CGRectGetMidX(rect), maxx = CGRectGetMaxX(rect) ;
CGFloat miny = CGRectGetMinY(rect), midy = CGRectGetMidY(rect), maxy = CGRectGetMaxY(rect) ;
minx = minx + 1;
miny = miny + 1;
maxx = maxx - 1;
maxy = maxy - 1;
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 0.6, 0.6, 0.6, 1.0, // Start color
0.3, 0.3, 0.3, 1.0 }; // End color
rgbColorspace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGRect currentBounds = [self bounds];
CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds));
CGPoint lowCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds));
CGContextDrawLinearGradient(c, glossGradient, topCenter, midCenter, 0);
CGGradientRelease(glossGradient);
CGColorSpaceRelease(rgbColorspace);
CGContextMoveToPoint(c, minx, midy);
CGContextAddArcToPoint(c, minx, miny, midx, miny, ROUND_SIZE_INFO);
CGContextAddArcToPoint(c, maxx, miny, maxx, midy, ROUND_SIZE_INFO);
CGContextAddArcToPoint(c, maxx, maxy, midx, maxy, ROUND_SIZE_INFO);
CGContextAddArcToPoint(c, minx, maxy, minx, midy, ROUND_SIZE_INFO);
// Close the path
CGContextClosePath(c);
// Fill & stroke the path
CGContextDrawPath(c, kCGPathFillStroke);
// return;
[super drawRect: rect];
What you are looking for is CGContextClip()
Create your path and gradient as in your code, and then
CGContextClip(c);
CGContextDrawLinearGradient(c, glossGradient, topCenter, midCenter, 0);
CGGradientRelease(glossGradient);
(Remove your old call to CGContextDrawLinearGradient)
Also remember to save and restore your context before and after you do any clipping
If your clipping is inverted from what you want, try CGContextEOClip() (or draw your path in reverse order)