How does CGContextFillPath in Objective C work? - objective-c

I have the following code which is supposed to draw a stroked and filled rectangle but the fill won't show up.
[[UIColor greenColor] setStroke];
[[UIColor brownColor] setFill];
CGContextBeginPath(context);
CGContextMoveToPoint(context, right, bottom);
CGContextAddLineToPoint(context, right,top);
CGContextAddLineToPoint(context,left, top);
CGContextAddLineToPoint(context, left, bottom);
CGContextAddLineToPoint(context, right, bottom);
CGContextStrokePath(context);
CGContextFillPath(context);
The stroke works and I get a nice green rectangle with no fill (or a white fill). This is within a UIView for iOS. Seems very simple and it's driving me nuts!

The right way to do this is to set the drawing mode to include both a fill and a path.
CGPathDrawingMode mode = kCGPathFillStroke;
CGContextClosePath( context ); //ensure path is closed, not necessary if you know it is
CGContextDrawPath( context, mode );
You can use CGContextFillPath() to simply do a fill, but it's basically just CGContextDrawPath() that automatically calls CGContextClosePath() and uses kCGPathFill as the CGPathDrawingMode. You might as well always use CGContextDrawPath() and put in your own parameters to get the type of fill/stroke that you want. You can also put holes in convex paths by using one of the Even-Odd drawing modes.

From the docs on CGContextStrokePath:
Quartz uses the line width and stroke color of the graphics state to
paint the path. As a side effect when you call this function, Quartz
clears the current path.

Probably you should call CGContextClosePath before you call CGContextFillPath

Related

Problems with borderless window content disappearing

If I remove this method from my view everything works fine (no content disappears if I click on a button), so this is definitely the cause.
I'm trying to make a window that is rounded and has a gradient via the code below. Is there anything wrong with this at all that could cause content on the view to disappear?
- (void)drawRect:(NSRect)dirtyRect
{
[NSGraphicsContext saveGraphicsState];
NSBezierPath *outerClip = [NSBezierPath bezierPathWithRoundedRect:[self bounds]
xRadius:3.0
yRadius:3.0];
[outerClip setClip];
NSGradient* aGradient = [[NSGradient alloc]
initWithStartingColor:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]
endingColor:[NSColor colorWithCalibratedWhite:0.65 alpha:1.0]];
[aGradient drawInRect:[outerClip bounds] angle:270];
[NSGraphicsContext restoreGraphicsState];
}
Switching to NSGradient drawInbezierPath fixes the issue.
I know you answered your own question but I thought I would share why your content was vanishing with the above code.
When you use setClip you are removing the previous clipping path and replacing it with your new path. This means you will wind up drawing outside of the dirty area, thus overwriting previously drawn content.
I had the same issue with drawing rounded corners on my own splash screen, and eventually found a different way to do what I wanted.
Also, you can use the clipRect: class method of NSBezierPath to change the clipping path to the intersection of the existing path and the area you want to restrict drawing into. Of course, save and restore the graphics state around your call to clipRect:.

NSBezierPath drawing

I want to do a rounded rectangle outline on an NSImage and I figured that using NSBezierPath would be the best way. However, I ran into a problem: instead of drawing a nice curve, I get this:
For reasons I can't understand, NSBezierPath is drawing the rounded part with a darker color than the rest.
Here's the code I'm using (inside a drawRect: call on a custom view):
NSBezierPath* bp = [NSBezierPath bezierPathWithRoundedRect: self.bounds xRadius: 5 yRadius: 5];
[[[NSColor blackColor] colorWithAlphaComponent: 0.5] setStroke];
[bp stroke];
Any ideas?
Edit:
If I inset the path by 0.5 everything draws just fine. But why is it that I get this when I offset the path by 10 pixels (for example)?
If I understand correctly, it should draw a thin line as well...
Many rendering systems are derived from the PostScript drawing model. Core Graphics is one of these derivative systems. (Here are some others: PDF, SVG, the HTML Canvas 2D Context, Cairo.)
All of these systems have the idea of stroking a path with a line of some fixed width. When you stroke the path, the line straddles the path: half of the line's width is on one side of the path, and half of the line's width is on the other side. Here's a diagram that may make this clearer:
Now, what happens when you stroke a path that lies along the boundary of your view? Half of the stroke falls outside of your view's bounds and is clipped away - not drawn. You only see the half of the stroke that falls inside the view's bounds.
When you use a rounded corner, that corner pulls away from the view's boundary, toward its center, so more of the stroke around the corner falls inside the view's boundary. So the stroke appears to get thicker around the rounded corner, like this:
To fix this, you need to inset your path by half the line width, so that the entire stroke falls inside your view's bounds along the entire path. The default line width is 1.0, so:
NSBezierPath* bp = [NSBezierPath bezierPathWithRoundedRect:
NSRectInset(self.bounds, 0.5, 0.5) xRadius:5 yRadius:5];
In iOS field, just minus the radius of the circle to prevent from being clipped.
UIBezierPath *roundPath = [UIBezierPath bezierPath];
[roundPath addArcWithCenter:
CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2)
radius:(self.frame.size.width / 2 - 0.5)
startAngle:M_PI_2 endAngle:M_PI * 3 / 2.f clockwise:YES];

Is UIBezierPath the best way to make a movable rounded rectangle?

I'm making a UISlider from scratch. I started by making a rounded rectangle, which I did using the code below:
CGRect frame = CGRectMake(10, 10, self.frame.size.width, 10);
UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:10.0];
[[UIColor blueColor] setFill];
[path fill];
I saw some other options to make a rounded rectangle but thought this was the quickest way. Are there any limitations with making one using UIBezierPath? Namely, the slider needs to be able to move upon touch events, so I want to change the center property of a BezierPath. Is this possible?
You would need to either recreate the bezier path each time you need to change the slider position, or use CGContext's transform matrix to draw it in a different place.
I suggest you look at using a CALayer for the moving part of the slider. Draw the channel of the slider in view.layer, and add a sublayer in which you draw the "thumb" of the slider. Then you can just reposition the thumb layer when you need to move it.

Horizontal Gradient with a rounded rect path?

I have a custom UIView I'm drawing using CoreGraphics. Using CoreGraphics, how would I set a horizontal gradient and clip it to my rounded rect path while still using a shadow? I wrote as much as the path and I can fill it with a color using setFill, but the gradient (a horizontal one, too) isn't abiding by the path...
EDIT: Since the time of posting (not so long ago), I figured out how to do a complex horizontal gradient, so now my only problem is drawing it within my CGContext's path.
Well it seems I've found my own answer:
CGContextAddPath(context, path);
CGContextClosePath(context);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPt, endPt, 0);
Those lines of code perfectly clip it to the path of a rounded rect, or any path desired.

How do I MOVE a circle drawn in a subclass of UIView by overwriting the method "drawRect"?

I'm trying to figure out what i'm doing wrong but i just don't get it. Here is what i want to do:
I want to draw a circle somewhere on the screen of the iPhone and then i want the circle always to be displayed at the position where the user currently taps on the screen.
I started by creating a subclass of UIView and adding the following lines into the "drawRect" method:
- (void)drawRect:(CGRect)rect {
//Create the main view!
CGContextRef mainscreen = UIGraphicsGetCurrentContext();
//Draw the dot
//will be a circle cause rectangle is a square
CGRect dotRect = CGRectMake(50, 80, 100, 100);
[[UIColor blueColor] set];
CGContextStrokeEllipseInRect(mainscreen, dotRect);
CGContextFillEllipseInRect(mainscreen, dotRect);
}
The appears just fine but now I have no idea how to make it move around on the screen I've tried several things and nothing worked.
To draw the dot in a different location, change the origin of dotRect. To figure out where to draw it, implement -touchesBegan:withEvent: and -touchesMoved:withEvent: and record the location where the touches are occuring.