I'm having a strange issue at the moment. I'm generating a drawing in a UIViewControllers UIView which calls drawRect:, goes through the entire code process without crashing but produces nothing. If I click on a UI object inside of the UIView the drawRect gets reloaded and my image appears.
The only good thing with this is that it happens every time at first load but it also happens some times later.
This usually works so am I missing something like a reload/set function?
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
CGContextSetRGBFillColor(context, 1, 1, 1, 1.0f);
CGContextFillRect(context, rect);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetLineWidth(context, 5.0);
CGContextMoveToPoint(context, 20, 20);
CGContextAddLineToPoint(context, 40, 40);
CGContextStrokePath(context);
Is your code calling -drawRect: directly? If so, stop that. Before calling -drawRect:, UIView sets up the drawing context so that all your -drawRect: override has to do is draw. If you try to call that method directly the drawing context won't be set up correctly and your drawing will end up in the wrong place, or nowhere at all. The drawing works correctly for you when you let the system manage drawing, in which case the context will be properly set before -drawRect: is called.
Sounds like your drawRect: code is not getting called when you expect it to be. Be sure you are calling setNeedsDisplay on your UIView as needed.
Related
On iOS, we can draw a line in drawRect using
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath (context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, 100, 100);
CGContextStrokePath(context);
but we can also draw a rectangle if we remove the above code, and just use:
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 100)];
[path stroke];
Two related questions:
1) Why doesn't UIBezierPath need to get or use the current context?
2) What if I have two context: one for screen, and one is a bitmap context, then how to tell which context to draw to for UIBezierPath? I thought it might be UIGraphicsSetCurrentContext but it doesn't exist.
UIBezierPath does use a context. It uses the current UIKit graphics context. This is exactly the same thing that you're already getting with UIGraphicsGetCurrentContext().
If you want UIBezierPath to use a different context you can use UIGraphicsPushContext(), but you have to remember to use UIGraphicsPopContext() when you're done.
I thought it might be useful to mention that CGContextFillRect is ~8.5x faster than using a UIBezierPath from what I can tell (in case performance is a factor and assuming you don't need to use a UIBezierPath for more complex drawing).
I added some timing to Apple's HazardMap example (http://developer.apple.com/library/ios/#samplecode/HazardMap/Introduction/Intro.html) and the time in ms per rect is ~0.00064 ms/rect for the CGContextFillRect approach versus ~0.00543 ms/rect for the UIBezierPath approach, presumably b/c the latter requires more message passing overhead.
i.e. I'm comparing using
CGContextFillRect(ctx, boundaryCGRect);
versus using
UIBezierPath* path = [UIBezierPath bezierPathWithRect:boundaryCGRect];
[path fill];
in the inner loop in HazardMapView (plus the above-mentioned changes to push / pop the context that is passed to HazardMapView drawMapRect:zoomScale:inContext:).
ETA
On iOS, we can draw a line in drawRect using
I've highlighted the important part of this statement. Inside of drawRect:, a context has already been set up for you by UIKit, and any object-based drawing instructions go directly into that context. UIBezierPath is indeed using that context, it just doesn't need to be passed in explicitly.
In Cocoa Touch, there must always be a drawing context (in this case, the context will eventually be painted onto the screen). If you were not inside drawRect:, you'd have to create a context yourself.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath (context);
CGContextMoveToPoint(context, 0, 0);
Notice that the first function call is GetCurrentContext(). When you're using CoreGraphics' functional drawing interface, you need to pass a context into each function, but you're not creating one here, you're just retrieving the one that already exists.
Graphics contexts are in a stack. If you want to draw into a context you've created, you push it onto the stack using UIGraphicsPushContext() (as Kevin already mentioned), then pop back to the previous one.
I am new to iphone programming.
In my app, I have to draw the lines very quickly according to my requirements. I am taking NSTimer with 0.01 as timing interval.
I am using the following code to draw the lines. How can I draw those lines quickly using NSTimer?
UIGraphicsBeginImageContext(bgImage.frame.size);
[bgImage.image drawInRect:CGRectMake(0, 0, bgImage.frame.size.width, bgImage.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(),5);
CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(),([UIColor blueColor]).CGColor);
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), startPoint.x, startPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), endPoint.x, endPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
bgImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
endpoint=startpoint;
can anybody help me?
If you want to present this in a view, you probably want to look at subclassing UIView, and placing this drawing code inside the drawRect: method.
drawRect: will have to re-draw the whole image each time, and may be called by the system whenever necessary. If you need to update it, call setNeedsDisplay: on the view, and it'll redraw at the end of the run loop.
Here's a sample view I have right now:
Ideally, I'd like to take a "chunk" out of the top, so any views underneath are now visible through that removed area (e.g. transparency).
I've tried creating a path, and then using CGContextClip to attempt a clip, but it doesn't seem to be clipping the shape like intended. Any ideas of how to do this, or even if it's possible at all?
Typically to do something like this, one would make a UIView with a transparent background color, and then draw the "background" manually through CoreGraphics. For instance, to make a view that is essentially a circle with a black background, you could do something like this in the drawRect method:
- (void)drawRect:(CGRect)dirtyRect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 0, 0, 0, 1);
CGContextFillElipseInRect(context, self.bounds);
}
For something more complicated than a simple circle, you should use CGContextBeginPath() in conjunction with the CGContextMoveToPoint() and CGContextAddLineToPoint() functions. This will allow you to make a transparent view with any opaque shape that you want for a background.
EDIT: To clip a background image to a certain path, you could do something like this:
CGContextSaveGState(context);
CGImageRef image = [[UIImage imageNamed:#"backgroundImage.png"] CGImage];
CGContextBeginPath(context);
// add your shape to the path
CGContextClipToPath(context);
CGContextDrawImage(context, self.bounds, image);
CGContextRestoreGState(context);
Obviously for repeating patterns you could use more than one call to CGContextDrawImage, but this is basically what needs to be done. Like I said above, you could use basic line drawing functions to add lines, rectangles, circles, and anything else to your path.
Use a [UIColor clearColor] in you V to make it transparent, you will probably have to set the view opaque to NO as well.
I have a UIView and within it I've drawn a line using Core Graphics by overriding drawRect. This view also contains one subview which also draws a line. However, whilst both views are using pretty much the same code (for testing purposes at least), the lines drawn on them do not appear the same:
As you can see - the dashed line at the top is noticeably thicker than the bottom one and I have no idea why. Below is the code used by the two UIViews in their drawRect methods. If you have any idea why this is happening then I'd appreciate your help and advice!
First View:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGFloat dashes[] = {1,1};
CGContextSetLineDash(context, 0.0, dashes, 2);
CGContextSetLineWidth(context, 0.6);
CGContextMoveToPoint(context, CGRectGetMinX(rect), CGRectGetMaxY(rect));
CGContextAddLineToPoint(context, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
CGContextStrokePath(context);
SubUIView *view = [[SubUIView alloc] initWithFrame:rect];
[self addSubview:view];
[view release];
The view is definitely only being drawn once. I appreciate drawRect may not be the best place for adding a subview but the problem remains even with it added in the main initWithFrame method.
Second View:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGFloat dashes[] = {1,1};
CGContextSetLineDash(context, 0.0, dashes, 2);
CGContextSetLineWidth(context, 0.6);
CGContextMoveToPoint(context, CGRectGetMinX(rect), CGRectGetMidY(rect));
CGContextAddLineToPoint(context, CGRectGetMaxX(rect), CGRectGetMidY(rect));
CGContextStrokePath(context);
It could be a result of anti-aliasing if your rect does not fall on integers. You can disable anti-aliasing with CGContextSetShouldAntialias( context, NO ). I think there's also a function for making a rect integral, but I can't remember what it is.
First, you should fix the problem of the drawing code being WET*. You say you're doing this “for testing purposes”, but this actually makes testing harder, since you have to change both pieces of code and/or keep straight which version you're working on. The worst case is when you change both pieces of code in different ways and have to merge them by hand.
I'd say move the dashed-line code to the subview, add properties for anything the two pieces of code need to do differently, and create two subviews (and not in drawRect:—seriously).
As for the actual problem: Well, I don't see a big image, I see a tiny image, and I can only guess that the greater boldness of the upper line than that of the lower line means that the upper line is thicker.
By the way, the rect is not necessarily the bounds of your image. Don't ever assume that it is, or you will get funky drawing when it isn't. Assume that it some section of the bounds—possibly, but possibly not, the whole thing. When you mean [self bounds], say [self bounds].
The problem is most likely the difference between CGRectGetMidY([self bounds]) and CGRectGetMaxY([self bounds]). One includes a fraction that splits a pixel, whereas the other is a multiple of one pixel or close to it. (Not necessarily a multiple of 1—on a Retina Display, 1 pt = 2 pixels, so 1 pixel = 0.5 pt.) Try flooring both numbers and optionally adding 0.5, and see which way you like better.
There's no way to make it work out perfectly with a 0.6-pt line width. There is simply no whole number of pixels that works out to. All you can do is work out what looks best and do that.
*Written Elsewhere Too, the opposite of DRY.
Why do Quartz 2D Graphics Contexts functions have to be called from within the drawRect method?
Because if I call a CGGraphics context function from anywhere except from within drawRect I get messages like this:
<Error>: CGContextFillRects: invalid context
<Error>: CGContextSetFillColorWithColor: invalid context
Actually in a subclass of UIView I do setup a Graphics Context in a method called Render. Bet when Render is called I get the above errors:
- (void)Render {
CGContextRef g = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(g, [UIColor blueColor].CGColor);
CGContextFillRect(g, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height));
CGContextSetFillColorWithColor(g, [UIColor blackColor].CGColor);
[#"It works!" drawAtPoint:CGPointMake(10.0, 20.0) withFont:[UIFont systemFontOfSize:[UIFont systemFontSize]]];
NSLog(#"gsTest Render");
}
They don't. Maybe you should elaborate a little more.
Cocoa sets up a context for you before calling your drawRect implementation. If you want to draw something somewhere else then that setup work is your responsibility.
UIGraphicsGetCurrentContext retrieves the graphics context from a stack which is set up by the framework. UIKit guarantees that when the drawRect method is called, a valid graphics context has been pushed onto this stack. After you return from it, this stack gets popped. If you call it outside the drawRect function it will not be valid.
Instead, if you want to call it outside drawRect, you need to create/obtain your own graphics context and draw onto that.
Some drawing functions, such as the NSString drawAtPoint:withFont: make use of this stack also; if the current context is not valid you will need to call UIGraphicsPushContext