I have some code in separate class, who bounded with UIView.
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect drawRect = CGRectMake(20, 262, 139, 169);
[self setFrame:drawRect];
CGContextSetLineWidth(context, 2);
CGContextSetStrokeColorWithColor(context, [[UIColor colorWithRed:125.0f/255.0f green:251.0f/255.0f blue:181.0f/255.0f alpha:1] CGColor]);
CGContextSetRGBFillColor(context, 200.0f/255.0f, 100.0f/255.0f, 100.0f/255.0f, 1.0f);
CGContextFillRect(context, drawRect);
But after clicking this view, size of UIView resizes to dimensions from interface builder model from my storyboard.
What does matter, and what's wrong with it?
drawRect: is not the place to change the frame. If the system can't automatically handle resizing the frame (by scaling the view, for instance), then drawRect: is called in response to resizing the frame. If you could change the frame size in the middle of drawing, you'd have an infinite loop.
If you want the view to be in the rectangle (20,262,139,169), then that's simple to put in autolayout. Just put the view where you want it, and create constraints to keep it there using Interface Builder.
If you don't want autolayout (not sure why you'd want to turn it off here, but if you did), you can turn it off and call setFrame: in viewDidLoad. But there's no reason to call it again in drawRect:.
Related
I am drawing image on a custom UIView. On resizing the view, the drawing performance goes down and it starts lagging.
My image drawing code is below:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIBezierPath *bpath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, width, height)];
CGContextAddPath(context, bpath.CGPath);
CGContextClip(context);
CGContextDrawImage(context, [self bounds], image.CGImage);
}
Is this approach correct?
You would be better using Instruments to find where the bottleneck is than asking on here.
However, what you will probably find is that every time the frame changes slightly the entire view will be redrawn.
If you're just using the drawRect to clip the view into an oval (I guess there's an image behind it or something) then you would be better off using a CAShapeLayer.
Create a CAShapeLayer and give it a CGPath then add it as a clipping layer to the view.layer.
Then you can change the path on the CAShapeLayer and it will update. You'll find (I think) that it performs much better too.
If your height and width are the same, you could just use a UIImageView instead of needing a custom view, and get the circular clipping by setting properties on the image view's layer. That approach draws nice and quickly.
Just set up a UIImageView (called "image" in my example) and then have your view controller do this once:
image.layer.cornerRadius = image.size.width / 2.0;
image.layer.masksToBounds = YES;
I'm working with autolayout and I have a RoundView class (subview of UIButton) which drawRect method is:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat lineWidth = 0.0;
CGContextSetLineWidth(context, lineWidth);
CGContextSetFillColorWithColor(context, _currentBackgroundColor.CGColor);
CGContextAddEllipseInRect(context, self.bounds);
CGContextFillPath(context);
}
Then I add it to self (a UIView), set its width and height using autolayout. Everything looks great.
But when I change its size constraints (to have a bigger view), and call
[self layoutIfNeeded];
in an animation block, the circle pixels look bigger and ugly.
Do I do this the correct way ?
I just got it from this post !
The problem was that the os wasn't calling drawRect at each animation step.
To force it, in RoundView's init:
self.contentMode = UIViewContentModeRedraw;
You need to take the content scale into account for the retina displays [UIDevice mainScreen].scale. See answers to this (question)[CGContext and retina display
I am new to these parts of iOS API and here are some questions that are causing an infinite loop in my mind
Why does ..BeginImageContext have a size but ..GetCurrentContext does not have a size? If ..GetCurrentContext does not have a size, where does it draw? What are the bounds?
Why did they have to have two contexts, one for image and one for general graphics? Isn't an image context already a graphic context? What was the reason for the separation (I am trying to know what I don't know)
UIGraphicsGetCurrentContext() returns a reference to the current graphics context. It doesn't create one. This is important to remember because if you view it in that light, you see that it doesn't need a size parameter because the current context is just the size the graphics context was created with.
UIGraphicsBeginImageContext(aSize) is for creating graphics contexts at the UIKit level outside of UIView's drawRect: method.
Here is where you would use them.
If you had a subclass of UIView you could override its drawRect: method like so:
- (void)drawRect:(CGRect)rect
{
//the graphics context was created for you by UIView
//you can now perform your custom drawing below
//this gets you the current graphic context
CGContextRef ctx = UIGraphicsGetCurrentContext();
//set the fill color to blue
CGContextSetFillColorWithColor(ctx, [UIColor blueColor].CGColor);
//fill your custom view with a blue rect
CGContextFillRect(ctx, rect);
}
In this case, you didn't need to create the graphics context. It was created for you automatically and allows you to perform your custom drawing in the drawRect: method.
Now, in another situation, you might want to perform some custom drawing outside of the drawRect: method. Here you would use UIGraphicsBeginImageContext(aSize)
You could do something like this:
UIBezierPath *circle = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(0, 0, 200, 200)];
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//this gets the graphic context
CGContextRef context = UIGraphicsGetCurrentContext();
//you can stroke and/or fill
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor lightGrayColor].CGColor);
[circle fill];
[circle stroke];
//now get the image from the context
UIImage *bezierImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *bezierImageView = [[UIImageView alloc]initWithImage:bezierImage];
I hope this helps to clear things up for you. Also, you should be using UIGraphicsBeginImageContextWithOptions(size, opaque, scale). For further explanation of custom drawing with graphics contexts, see my answer here
You are slightly confused here.
As the name suggests UIGraphicsGetCurrentContext grabs the CURRENT context, thus it doesn't need the size, it grabs an existing context and returns it to you.
So when is there an existing context? Always? No. When the screen is rendering a frame, a context is created. This context is available in the drawRect: function, which is called to draw the view.
Normally, your functions aren't called in drawRect:, so they don't actually have a context available. This is when you call UIGraphicsBeginImageContext.
When you do that, you create an image context, then you can grab said context with UIGraphicsGetCurrentContext and work with it. And thus, you have to remember to end it with UIGraphicsEndImageContext
To clear things up further - if you modify the context in drawRect:, your changes will be shown on screen. In your own function, your changes don't show up anywhere. You have to extract the image in the context through the UIGraphicsGetImageFromCurrentImageContext() call.
Hope this helps!
I have a UIView where I would like to draw a Circle that extends past the frame of the UIView,
I have set the masksToBounds to NO - expecting that I can draw past outside the bounds of the UIView by 5 pixels on the right and bottom.
I expect the oval to not get clipped but it does get clipped and does not draw outside the bounds?
- (void)drawRect:(CGRect)rect
{
int width = self.bounds.size.width;
int height = self.bounds.size.height;
self.layer.masksToBounds = NO;
//// Rounded Rectangle Drawing
//// Oval Drawing
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(0, 0, width+5, height+5)];
[[UIColor magentaColor] setFill];
[ovalPath fill];
[[UIColor blackColor] setStroke];
ovalPath.lineWidth = 1;
[ovalPath stroke];
}
From http://developer.apple.com/library/ios/#documentation/general/conceptual/Devpedia-CocoaApp/DrawingModel.html
UIView and NSView automatically configure the drawing environment of a
view before its drawRect: method is invoked. (In the AppKit framework,
configuring the drawing environment is called locking focus.) As part
of this configuration, the view class creates a graphics context for
the current drawing environment.
This graphics context is a Quartz object (CGContext) that contains
information the drawing system requires, such as the colors to apply,
the drawing mode (stroke or fill), line width and style information,
font information, and compositing options. (In the AppKit, an object
of the NSGraphicsContext class wraps a CGContext object.) A graphics
context object is associated with a window, bitmap, PDF file, or other
output device and maintains information about the current state of the
drawing environment for that entity. A view draws using a graphics
context associated with the view’s window. For a view, the graphics
context sets the default clipping region to coincide with the view’s
bounds and puts the default drawing origin at the origin of a view’s
boundaries.
Once the clipping region is set, you can only make it smaller. So, what you're trying to do isn't possible in a UIView drawRect:.
I'm not certain this will fix your problem, but it's something to look into. You're setting self.layer.masksToBounds = NO every single time you enter drawRect. You should try setting it inside the init method just once instead, A) because it's unnecessary to do it multiple times and B) because maybe there's a problem with setting it after drawRect has already been called--who knows.
I have a map custom view that inherit from MKOverlayPathView. I need this custom view to display circle, line and text.
I already managed to draw circle and line using path drawing CGPathAddArc and CGPathAddLineToPoint functions.
However, I still need to add text.
I tried to add text using
[text drawAtPoint:centerPoint withFont:font];
but I got invalid context error.
any idea?
With MKOverlayPathView, I think the easiest way to add text is to override drawMapRect:zoomScale:inContext: and put the path and text drawing there (and do nothing in or don't implement createPath).
But if you're going to use drawMapRect anyway, you might want to just switch to subclassing a plain MKOverlayView instead of MKOverlayPathView.
With an MKOverlayView, override the drawMapRect:zoomScale:inContext: method and draw the circle using CGContextAddArc (or CGContextAddEllipseInRect or CGPathAddArc).
You can draw the text using drawAtPoint in this method which will have the required context.
For example:
-(void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context
{
//calculate CG values from circle coordinate and radius...
CLLocationCoordinate2D center = circle_overlay_center_coordinate_here;
CGPoint centerPoint =
[self pointForMapPoint:MKMapPointForCoordinate(center)];
CGFloat radius = MKMapPointsPerMeterAtLatitude(center.latitude) *
circle_overlay_radius_here;
CGFloat roadWidth = MKRoadWidthAtZoomScale(zoomScale);
//draw the circle...
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetFillColorWithColor(context, [[UIColor blueColor] colorWithAlphaComponent:0.2].CGColor);
CGContextSetLineWidth(context, roadWidth);
CGContextAddArc(context, centerPoint.x, centerPoint.y, radius, 0, 2 * M_PI, true);
CGContextDrawPath(context, kCGPathFillStroke);
//draw the text...
NSString *text = #"Hello";
UIGraphicsPushContext(context);
[[UIColor redColor] set];
[text drawAtPoint:centerPoint
withFont:[UIFont systemFontOfSize:(5.0 * roadWidth)]];
UIGraphicsPopContext();
}
In relation to a comment in another answer...
When the center coordinate or radius (or whatever) of the associated MKOverlay changes, you can make the MKOverlayView "move" by calling setNeedsDisplayInMapRect: on it (instead of removing and adding the overlay again). (When using a MKOverlayPathView, you can call invalidatePath instead.)
When calling setNeedsDisplayInMapRect:, you can pass the boundingMapRect of the overlay for the map rect parameter.
In the LocationReminders sample app from WWDC 2010, the overlay view uses KVO to observe changes to the associated MKOverlay and makes itself move whenever it detects a change to the circle's properties but you could monitor the changes in other ways and call setNeedsDisplayInMapRect: explicitly from outside the overlay view.
(In a comment on another answer I did mention using MKOverlayPathView and that is how the LocationReminders app implements a moving circle overlay view. But I should have mentioned how you can also use MKOverlayView to draw a circle. Sorry about that.)
Pushing the context with UIGraphicsPushContext generated a problem for me. Remind that the method drawMapRect:zoomScale:inContext: is called from different threads in the same time so I had to synchronize the piece of code starting where the UIGraphicsPushContext is called down to UIGraphicsPopContext call.
Also when calculating the font size like in [UIFont systemFontOfSize:(5.0 * roadWidth)] one should take into consideration the [UIScreen mainScreen].scale, which for iPad, iPad2, iPhone3 is 1 and for iPhone4 - 5 and iPad3 is 2. Otherwise the text size will be different from iPad2 to iPad3.
So for me it ended like this: [UIFont boldSystemFontOfSize:(6.0f * [UIScreen mainScreen].scale * roadWidth)]