Very frustrating experience understanding UIGraphicsPushContext(context) and UIGraphicsPopContext()
My understanding is that I can set attributes of the context like the stroke color and then push the context on a stack so I can set a new color for the current context. When I'm done doing that I can return to the context by popping it.
Below is my code. When I run the code below two lines are drawn in blue. What I expect to happen is: that I first set the color green. Go to the blueLine function and push the green context. Draw in blue. Then pop the green context. Allowing the drawLine function to drawn in green.
Here is a screenshot of what is drawn (two blue lines): http://dl.dropbox.com/u/1207310/iOS%20Simulator%20Screen%20shot%20Feb%205%2C%202012%209.00.35%20PM.png
Any help is GREATLY appreciated! Thank you.
- (void)drawBlueLine:(CGContextRef)context
{
UIGraphicsPushContext(context);
[[UIColor blueColor] setStroke];
CGContextBeginPath(context);
CGContextMoveToPoint(context, self.bounds.origin.x, 100);
CGContextAddLineToPoint(context, self.bounds.origin.x+self.bounds.size.width, 200);
CGContextStrokePath(context);
UIGraphicsPopContext();
}
- (void)drawLine:(CGContextRef)context
{
UIGraphicsPushContext(context);
CGContextBeginPath(context);
CGContextMoveToPoint(context, self.bounds.origin.x, 200);
CGContextAddLineToPoint(context, self.bounds.origin.x+self.bounds.size.width, 300);
CGContextStrokePath(context);
UIGraphicsPopContext();
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor redColor] setStroke];
CGContextSetLineWidth(context, 5.0);
[self drawBlueLine:context];
[self drawLine:context];
}
Or shouldn't this work?
- (void)drawLine:(CGContextRef)oldContext
{
UIGraphicsPushContext(oldContext); //makes oldContext the current context but there is a copy on the stack
[[UIColor blueColor] setStroke];
CGContextBeginPath(oldContext);
CGContextMoveToPoint(oldContext, self.bounds.origin.x, 200);
CGContextAddLineToPoint(oldContext, self.bounds.origin.x+self.bounds.size.width, 300);
CGContextStrokePath(oldContext);
UIGraphicsPopContext(); //previous oldContext is moved back into place containing red?
}
- (void)drawRect:(CGRect)rect
{
CGContextRef oldContext = UIGraphicsGetCurrentContext();
[[UIColor redColor] setStroke];
[self drawLine:oldContext];
//anything I draw here should be red. Shouldn't it?`
What UIGraphicsPushContext and UIGraphicsPopContext does as change which context is being used for drawing, not necessarily save and restore any given context's state. What your code is doing is simply repetitively replacing the context that was current prior to the call to drawRect: with the context you grab with UIGraphicsGetCurrentContext (they are likely to be the same anyway).
If you want to save an restore a given context's state, use CGContextSaveGState and CGContextRestoreGState instead.
The problem is the way you are setting the color, you need to use
CGContextSetStrokeColorWithColor(context, color)
like this:
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor)
and
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor)
so that the color is connected to the context.
[[UIColor redColor] setStroke];
is changing the color for all strokes...I'm curious if it would overide the above function if it was set for the context, I think it would not...
Related
I have a drawRect and I want to fill in the rect specified with a certain color. How do I do so? So far I have tried the following:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor colorWithWhite:29/255.f alpha:1.0].CGColor);
CGContextFillRect(context, rect);
}
This doesn't seem to work
[[UIColor colorWithWhite:29/255.f alpha:1.0] setFill];
UIRectFill(rect);
This is the simplest way
I am drawing a custom cell, to achieve a certain look I want. However, I want to perform a different drawing based on if the cell is select. I really do not just want the default colors.
I changed content's views background color the view in this method:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
However, it just did not appear correctly, mainly it did not take into account the accessary and just colored it until the accessory indicator. Is there a better way to accomplish this?
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
// Background
CGContextSetFillColorWithColor(context, CELL_BACKGROUND_COLOR);
CGContextMoveToPoint(context, 0.0f, 0.0f);
CGContextAddLineToPoint(context, rect.size.width, 0.0f);
CGContextAddLineToPoint(context, rect.size.width, rect.size.height);
CGContextAddLineToPoint(context, 0.0f, rect.size.height);
CGContextClosePath(context);
CGContextFillPath(context);
// Top line
CGContextSetStrokeColorWithColor(context, CELL_TOP_LINE_COLOR);
CGContextSetLineWidth(context, CELL_LINE_WIDTH);
CGContextSetLineCap(context, kCGLineCapSquare);
CGContextMoveToPoint(context, 0.0f, 0.0f);
CGContextAddLineToPoint(context, rect.size.width, 0.0f);
CGContextStrokePath(context);
//Bottom line
CGContextSetStrokeColorWithColor(context, CELL_BOTTOM_LINE_COLOR);
CGContextSetLineWidth(context, CELL_LINE_WIDTH);
CGContextSetLineCap(context, kCGLineCapSquare);
CGContextMoveToPoint(context, 0.0f, rect.size.height);
CGContextAddLineToPoint(context, rect.size.width, rect.size.height);
CGContextStrokePath(context);
}
I think you should modify your drawRect method and change how your color are set depending of the isSelected property. Changing the content's view background when the setSelected method will probably don't change anything since it will be overriding by the drawRect method.
I'm using this :
if(self.highlighted || self.selected) {
//set text color
textColor = [UIColor colorWithRed:204.0/255.0 green:255.0/255.0 blue:0 alpha:1.0];
//set background color
[[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.7] set];
CGContextFillRect(context, r);
}
Also check selectionStyleproperty of your cell.
I found a solution that works great, it also draws underneath the default accessory views, like the accessory indicator. I created custom view's called BackgroundView and SelectedBackgroundView, I just use the drawRect method to create a custom drawing. It works great, and performance seems to be okay. If anyone wants to see the full code let me know.
[cell setBackgroundView:[[BackgroundView alloc] init]];
[cell setSelectedBackgroundView:[[SelectedBackgroundView alloc] init]];
I've been going through the Standford University IOS lessons and I am stuck on understanding what UIGraphicsPushContext and UIGraphicsPopContext does (lesson 4). From the notes, it is explained that these methods can be used to avoid affecting the current graphics states from other utility methods. Here is the example provided in the notes:
- (void)drawGreenCircle:(CGContextRef)ctxt {
UIGraphicsPushContext(ctxt);
[[UIColor greenColor] setFill];
// draw my circle
UIGraphicsPopContext();
}
- (void)drawRect:(CGRect)aRect {
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor redColor] setFill];
// do some stuff
[self drawGreenCircle:context];
// do more stuff and expect fill color to be red
}
However, when I try to test this, I don't seem to get this result. Below is a simple test case I've made that gets the current context in drawRect, sets the color, calls a utility method that sets the color to green, returns to drawRect and draws a line. I would expect based on the notes on the Standford site that the line would be red since I pushed/popped the context in my drawGreenCircle. (I realize I am not actually drawing any circle in drawGreenCircle) But I get a green line in drawRect. It seems that my drawGreenCircle method did change the color and did not revert it back to red. I would like to know what I am missing about this concept.
- (void)drawGreenCircle:(CGContextRef)ctxt {
//Doesn't actually draw circle --Just testing if the color reverts
//to red in drawRect after this returns
UIGraphicsPushContext(ctxt);
[[UIColor greenColor] setStroke];
UIGraphicsPopContext();
}
- (void)drawRect:(CGRect)aRect {
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor redColor] setStroke];
// do some stuff
[self drawGreenCircle:context];
CGContextBeginPath(context);
CGContextMoveToPoint(context, 100, 100);
CGContextAddLineToPoint(context, 500, 100);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}
You need CGContextSaveGState and CGContextRestoreGState instead. The following code works:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor greenColor] setFill];
[[UIColor blackColor] setStroke];
[self drawBlueCircle:context];
CGContextMoveToPoint(context, 55, 5);
CGContextAddLineToPoint(context, 105, 100);
CGContextAddLineToPoint(context, 5, 100);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}
- (void)drawBlueCircle:(CGContextRef)context {
CGContextSaveGState(context);
[[UIColor blueColor] setFill];
CGContextAddEllipseInRect(context, CGRectMake(0, 0, 100, 100));
CGContextDrawPath(context, kCGPathFillStroke);
CGContextRestoreGState(context);
}
Source
I am trying to make an iPhone application with erasing. I am running into 2 problems, if you have a solution for either one please answer this. I would like to erase part of an image.
1) I am currently just clearing the rect but it has a square edge. I would like it to be round, I have previously tried translating but this does not work. I also need it to translate/rotate as few times as possible to maintain the same performance.
2) In addition I wanted to know if there are any other ways of erasing. When erasing fast it is erasing ever 1/2 inch. Is there a way to stroke a path and clear the rect or something? Sorry if this is hard to understand.
CGRect circleRect = CGRectMake([touch CGPointValue].x, [touch CGPointValue].y, 25, 25);
CGContextClearRect(currentContext,circleRect);
This code should do what you're looking for:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.strokeWidth);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextSetStrokeColorWithColor(context, [[UIColor clearColor] CGColor]);
CGContextBeginPath(context);
CGContextMoveToPoint(context, lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(context, currentPoint.x, currentPoint.y);
CGContextStrokePath(context);
CGContextFlush(context);
The key points are
1) CGContextSetLineCap(context, kCGLineCapRound), which makes it round
2) CGContextSetBlendMode(context, kCGBlendModeClear) which clears the context.
I realize this is an older question but I thought I would expand on Eric Reids answer as I was trying to use his example outside of the drawRect method and had to modify it to get it to work with a context I created in a 'touchesMoved' method in a UIView subclass.
I call this method from 'touchesMoved' and pass it the CGPoint of the touch in the view I'm drawing in.
-(void) processEraseAtPoint:(CGPoint)point
{
// setup a context with the size of our canvas view (the canvas view is the UIView instance I'm drawing into)
UIGraphicsBeginImageContext(self.canvasView.bounds.size);
// get a reference to the context we just created
CGContextRef context = UIGraphicsGetCurrentContext();
// draw the image we want to edit into this context (this is the image containing the drawing I want to erase part of)
[self.canvasView.incrementalImage drawAtPoint:CGPointZero];
// set our context options
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.canvasView.settings.brushDiameter);
CGContextSetBlendMode(context, kCGBlendModeClear);
// make the color clear since we're erasing
CGContextSetStrokeColorWithColor(context, [[UIColor clearColor] CGColor]);
// start our path in this context
CGContextBeginPath(context);
// set our first point
CGContextMoveToPoint(context, lastTouch.x, lastTouch.y);
// draw from our last point to this point
CGContextAddLineToPoint(context, point.x, point.y);
// stroke this path (in this case it's clear so it will erase what's there)
CGContextStrokePath(context);
// set our incrementalImage in the canvasView with the updated image from this context
// Note that in the canvasView 'drawRect' method I am calling
// '[self.incrementalImage drawInRect:rect]', so this new image will get drawn
// in my canvasView when I call 'setNeedsDisplay'
self.canvasView.incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
// cleanup our context
CGContextFlush(context);
UIGraphicsEndImageContext();
// set our last touch point for the next line segment
lastTouch = point;
// update our view
[self.canvasView setNeedsDisplay];
}
I am new to Objective-C and iOS development. I have a view with some figures drawn by core drawing. Now I want to fill those shapes with color and I don't know the path or context of shape. Does Objective-C have any function like flood-fill or put-pixel so that by having only the stroke color I can fill any shape inside my view.
-(void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 4.0);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = {1.0, 0.0, 0.0, 1.0};
CGColorRef color = CGColorCreate(colorspace, components);
CGContextSetStrokeColorWithColor(context, color);
CGContextMoveToPoint(context, 50, 0);
CGContextAddLineToPoint(context, 0,320);
CGContextStrokePath(context);
CGColorSpaceRelease(colorspace);
CGColorRelease(color);
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetRGBFillColor(context, 0, 255, 0, 1.0);
CGContextMoveToPoint(context, 100, 100);
CGContextAddLineToPoint(context, 150, 150);
CGContextAddLineToPoint(context, 100, 200);
CGContextAddLineToPoint(context, 50, 150);
CGContextAddLineToPoint(context, 100, 100);
CGContextStrokePath(context);
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGRect rectangle = CGRectMake(60,170,200,80);
CGContextAddEllipseInRect(context, rectangle);
CGContextStrokePath(context);
}
This will make a circle and a square with some common region. I want a particular region to fill with color when a user taps that region.
So you are looking for flood fill in Objective C.
This is my implementation of Scan Line Flood Fill Algorithm.
Github : UIImageScanlineFloodfill
If you want to learn basic of flood fill:
Lode's Computer Graphics Tutorial Flood Fill
I hope this will help you.
Put your drawing code in the question and I can give you a specific answer.
Normally you just need to set a fill color using
[[UIColor redColor] setFill];
And any shapes you draw your core graphics will be filled with that color automatically.
Try this command:-
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
Core Graphics is closely tied to UIView and its drawRect: method and has a C interface. As you point out, you always draw in the current graphics context. I can think of 2 approaches: (1) When the user taps myView, set a property like myView.pointTapped, and in drawRect: find the shape to fill, set a fill color, and fill the closed path using GGGeometry methods.
There is an objective-C alternative: (2) Take a look at UIBezierPath. It's not as comprehensive as drawing in Core Graphics but it can be used in your ViewController implementation.