I am drawing a circle in my custom UIView's drawRect:
- (void)drawRect:(CGRect)rect {
...
UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect:rect];
[UIColor.whiteColor setStroke];
ovalPath.lineWidth = 1;
[ovalPath stroke];
}
The oval is always clipped on the edge. How can I avoid the clipping? Is insetting the rect the only way?
CG draws the stroke centered on the path -- half inside the path, half outside the path. Therefore, part of the stroke is outside of your view, and you don't see it.
Inset the rect by half the stroke width.
CGRect rectToStroke = CGRectInset(rect, 0.5, 0.5);
UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect: rectToStroke];
Anything you draw inside of -drawRect: using CG or UIKit goes into a bitmap context which is the size of your view's bounds.
If you need to show something bigger than bounds.size, you have two options: make the view's bounds bigger, or draw via some other method, such as:
add a subview which is bigger (but it will appear on top of your view, so you'll need to make it partially transparent)
add a CALayer or CAShapeLayer to your view's layer (with the same caveat)
set your view's layer's borderWidth to draw a border on top of the contents
(For all of these, you may find that you also need to set your view's clipsToBounds property to NO, if it isn't already.)
Related
I am testing a UIView using a UISlider as in the example images below:
I have a custom UIView with a yellow background that draws the gray square, the drawRect method is like so:
-(void)drawRect:(CGRect)rect{
NSLog(#"Draw rect called");
UIBezierPath* squarePath = [UIBezierPath bezierPathWithRect: CGRectMake(10, 10, 100, 100)];
[UIColor.grayColor setFill];
[squarePath fill];
}
And the method for my slide changing value:
- (IBAction)changeValue:(id)sender {
CGAffineTransform transform = CGAffineTransformScale(CGAffineTransformIdentity, self.slider.value, self.slider.value);
self.tableView.transform = transform;
[self.tableView setNeedsDisplay];
}
I dont understand why the square is getting larger. I've noticed that drawRect is called every time the slider is moved. If this happens then why is the square size changing? Shouldn't it remain the same size and just the frame grow with the square in the top left corner?
My second question is, how would I change the code so just the frame grows and the drawing size stays the same? I ask this because actually I want the drawing size to change dynamically using my own code in drawRect.
Any pointers would be really appreciated! thanks!
The reason why the size of the square changes is because you've transformed it. Transformations don't just affect the frame of a view; they will affect the content. The square is getting drawn into its context at its constant size (100x100) and then the transform is stretching it before it gets rendered.
The reason why it's not expanding to the right and down is because by default the anchor point of a transform is the center of the bounds. Thus it'll scale from the center outwards. From the documentation:
The origin of the transform is the value of the center property ...
Transformations aren't intended to be used to simply scale the width and height of your frame. The frame property is for that. Simply store the view's frame in a variable, change its width and height, then set it back. In your drawRect: code you can check the dimensions of the rectangle that's given to you and make your square's width/height a percentage of that.
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 trying to add some shadow under a UIView.
In drawRect I created a rounded rectangle bezier path and appended a curved arrow-like bottom part to it (blue stuff)
Here is the shadow's code :
...
CGContextSaveGState(context);
CGContextAddPath(context, rectPath.CGPath); // rectPath is the bezier
CGContextSetShadowWithColor(context,shadowSize, 3.0, [[[UIColor blackColor] colorWithAlphaComponent:0.7]CGColor]);
CGContextFillPath(context);
CGContextRestoreGState(context);
...
As you can see, the shadow is cut off at the bottom, that's where the view's frame ends. Can I extend the shadow "outside" the frame ?
use the CALayer shadow properties instead of drawing the shadow in drawRect:
shadowOpacity
shadowRadius
shadowOffset
shadowColor
shadowPath
this can result in some performance issues, which you can reduce by using the shadowPath property.
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'm trying to draw a custom button frame as follows:
UIBezierPath *stroke = [UIBezierPath bezierPathWithRoundedRect:self.bounds
cornerRadius:RECT_CORNECR_RADIUS];
[stroke stroke];
But for some reason the corner curves look thinker than the sides. If you look at the UIButton's default frame it's very uniform. A is a UIButton, B is a custom button.
Any ideas how I can make it more like the UIButton.
You are stroking the bounds of your button. This will draw your line centred over the edge the view, so half of the thickness of the line is outside the bounds and is not drawn. This is why it is full thickness in the corners. Use CGRectInset on your bounds rectangle (inset by half the thickness of your line) and stroke THAT rect.
The problem you have is probably due to antialiasing. You can try to change the antialiasing settings of CoreGraphics before drawing your beizerPath.
An easier solution is to use the CALayer of your button and its cornerRadius property. It would be easier to draw a rounded corner
If self is your custom button:
self.layer.cornerRadius = RECT_CORNER_RADIUS;
self.layer.borderWidth = 1.0f;
self.layer.borderColor = [UIColor blackColor].CGColor;
Of course don't forget to import the QuartzCore framework and import its header for this to work (#import )