Where's my Bezier path? - ios

I am trying to draw a Bezier path, an arc of a given angle, in response to a pan gesture. Here's what I have inside the function called by the pan gesture:
UIGraphicsBeginImageContext(self.frame.size);
UIBezierPath *path = [self createArcPath];
[[UIColor blackColor] setStroke];
[[UIColor redColor] setFill];
CGContextRef *ref = UIGraphicsGetCurrentContext();
path.lineWidth = 5;
[path fill];
[path stroke];
UIGraphicsEndImageContext();
And here is createArcPath:
- (UIBezierPath *)createArcPath
{
UIBezierPath *aPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(150, 150)
radius:75
startAngle:0
endAngle:DEGREES_TO_RADIANS(135)
clockwise:YES];
return aPath;
}
I don't get an error, but I just don't get anything on my screen relating to the arc. What am I missing? This is in a method of a UIView subclass.
I also tried the following method in - (id)initWithFrame:
UIColor *color =[UIColor redColor];
UIGraphicsBeginImageContext(self.frame.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, width);
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextSetStrokeColorWithColor(context, color.CGColor);
CGRect myOval = {100, 100, self.radius, self.radius};
CGContextAddEllipseInRect(context, myOval);
UIGraphicsEndImageContext();
I also don't see anything. What am I missing? Can these methods not be called from a UIView?

The UIGraphicsBeginImageContext/UIGraphicsEndImageContext calls create an off-screen context that you can draw into, then extract an image from. Since it's an off-screen context nothing shows on the screen.
What you probably want to do is create a custom subclass of UIView that overrides drawRect. In your code that responds to user gestures, change the data structure(s) that represent your drawing, then call setNeedsDisplay to trigger the system to ask your view to redraw itself by calling your drawRect method.
As an alternative you can set up your custom view to install a CAShapeLayer in the view, then change the path on the layer, then call setNeedsDisplay on the layer.

Related

Changing a fill color on ellipse using Objective-C

I am overriding drawRect method in order to draw ellipse
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, [UIColor mainColorYellow].CGColor);
CGRect rectangle = CGRectMake(0.0, 2.0, rect.size.width , rect.size.height - 4.0);
CGContextAddEllipseInRect(context, rectangle);
CGContextStrokePath(context);
}
How can I change fill color on ellipse? I tried using CGContextSetFillColorWithColor(CGContextRef c, CGColorRef color) but without result.
There is two different operations, fill and stroke. Stroke es the border, fill is the inside. You are only doing the border. You can control the fill and border color independently. You have, however, to render both explicitly. For example:
[[UIColor yellowColor] setFill];
[[UIColor blackColor] setStroke];
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0.0, 2.0, rect.size.width , rect.size.height - 4.0)];
path.lineWidth = 2.0;
[path fill];
[path stroke];
Notice I wrote the code in a more modern quartz. The context is implicit. Hope it helps.
Just add below:
CGContextFillEllipseInRect(context, rectangle);

UIImageView sides and top rounded borders

Hi all I need to realize borders around an UIImageView like these.
Left, top and right border like an half-moon
http://imageshack.com/a/img841/3269/o90p.png
How can i do it ?
You can user CALayer for the same.
CALayer *l = [_btn layer];
[l setMasksToBounds:YES];
[l setCornerRadius:8.0];
and add QuartzCore.framework
You may want to subclass UIView and override the drawRect: method. Then place add your image view as a subview. Your drawRect: method would look something like this (with clipsToBounds set to YES):
- (void)drawRect:(CGRect)rect
{
UIColor *fillColor = [UIColor clearColor];
UIColor *borderColor = [UIColor redColor];
float borderWidth = 2.0f;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, fillColor.CGColor);
CGContextSetLineWidth(context, borderWidth);
CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
CGContextMoveToPoint(context, CGRectGetMinX(rect), CGRectGetMidY(rect));
CGContextAddArc(context, CGRectGetMidX(rect), CGRectGetMidY(rect), rect.size.height/2, 2*M_PI, M_PI, 1);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}
Alternatively you can look into masking with a semicircle image or creating a CAShapeLayer for the image view.
You need to:
Create a CAShapeLayer
Set its path to be a CGPathRef based on view.bounds but with only two rounded corners (probably by using [UIBezierPath
bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:])
Set your view.layer.mask to be the CAShapeLayer
Just take in account that this have a bad effect on performance. This might help

UIBezierPaths Not Showing in CALayer drawLayer inContext

I am using the code below to draw an arc into the drawLayer method of a custom CALayer class but nothing is displayed:
(void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
float r = self.bounds.size.width/2;
CGContextClearRect(ctx, self.bounds); // clear layer
CGContextSetFillColorWithColor(ctx, [UIColor whiteColor].CGColor);
//CGContextFillRect(ctx, layer.bounds);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(0, 0) radius:r startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:NO];
CGContextAddPath(ctx, path.CGPath);
CGContextStrokePath(ctx);
}
Note that if I uncomment the CGContextFillRect(ctx, layer.bounds) line, a rectangle is properly rendered.
The problem I can see is that you don't set a stroke color (but a fill color)
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
When you configure a fill color it is used to fill the path. Same for a stroke color.
If you want to only want to either stroke or fill the path then you should use either CGContextStrokePath or CGContextFillPath but if you want to do both then you should use CGContextDrawPath with kCGPathFillStroke as the "drawing mode".
Thanks to both of you. I finally decided to draw the arc outside the drawLayer method using the following code:
- (id) initWithLayer:(id)layer withRadius:(float)radius
{
self = [super initWithLayer:layer];
// ...
self.strokeColor = [UIColor whiteColor].CGColor;
self.lineWidth = 10.0;
return self;
}
- (void) update:(float)progress
{
// ....
UIBezierPath *arcPath = [UIBezierPath bezierPath];
[arcPath addArcWithCenter:CGPointMake(r, r) radius:r - self.lineWidth/2 startAngle:startAngle endAngle:nextAngle clockwise:YES];
self.path = arcPath.CGPath;
}
It looks like all the arc drawing methods for both CGPath and UIBezierPath have a bug on 64 bit devices where they only work if the clockwise parameter is set to YES. I notice that your non-working code shows clockwise:NO and the working code has clockwise:YES.

Invalid context in drawRect

I'm trying to draw some bezier paths in my view's drawRect method, but I keep getting an invalid context error. I don't understand why, because I thought that there was always a context when drawRect is called. Am I missing something? This is the full implementation I have:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat pattern[] = {5.0, 3.0};
CGContextSetLineDash(context, 0.0, pattern, 2);
// Set line color to be black
[[UIColor blackColor] setStroke];
//Stroke the bezier path for each plane
for(SEPlaneView *plane in self.planes){
// Set line thickness and termination style
[plane.planeBezier setLineWidth:1.0];
[plane.planeBezier setLineCapStyle:kCGLineCapRound];
[plane.planeBezier stroke];
}
CGContextRelease(context);
}

How to Get the reverse path of a UIBezierPath

self.myPath=[UIBezierPath bezierPathWithArcCenter:center
radius:200
startAngle:0
endAngle:180
clockwise:YES];
(This much I was able to get up running with some web searching).
I have this path. Now I want to fill the reverse of this path, so leaving this portion and filling everything else. How can I finish the coding? I don't have much info on this.
The problem
The area it is showing after using Cemal Answer previously it only showed a circle with red stroke.
Edit
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor whiteColor];
self.punchedOutPath =
[UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 400, 400)];
self.fillColor = [UIColor redColor];
self.alpha = 0.8;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
[[self fillColor] set];
UIRectFill(rect);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(ctx, kCGBlendModeDestinationOut);
[[self punchedOutPath] fill];
CGContextSetBlendMode(ctx, kCGBlendModeNormal);
}
Use bezierPathByReversingPath. From the docs (iOS 6.0+ only):
Creates and returns a new bezier path object with the reversed contents of the current path.
so to reverse your path, you'd just:
UIBezierPath* aPath = [UIBezierPath bezierPathWithArcCenter:center
radius:200
startAngle:0
endAngle:180
clockwise:YES];
self.myPath = [aPath bezierPathByReversingPath];
Here's an alternative that doesn't require reversing the path at all.
You have a portion of a view you essentially want to "clip out":
Let's say you want the white area to be [UIColor whiteColor] with 75% alpha. Here's how you do it quickly:
You create a new UIView subclass.
This view has two properties:
#property (retain) UIColor *fillColor;
#property (retain) UIBezierPath *punchedOutPath;
You override its -drawRect: method to do this:
- (void)drawRect:(CGRect)rect {
[[self fillColor] set];
UIRectFill(rect);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(ctx, kCGBlendModeDestinationOut);
[[self punchedOutPath] fill];
CGContextSetBlendMode(ctx, kCGBlendModeNormal);
}
There's a caveat here: The fillColor of the view must not include the alpha component. So in your case, you'd want that to just be [UIColor whiteColor]. You then apply the alpha bit yourself by calling [myView setAlpha:0.75].
What's going on here: This is using a blend mode called "Destination Out". Mathematically it's defined as R = D*(1 - Sa), but in layman's terms it means "Destination image wherever destination image is opaque but source image is transparent, and transparent elsewhere."
So it's going to use the destination (i.e., what's already in the context) wherever the new stuff is transparent (i.e. outside of the bezier path), and then where the bezier path would be opaque, that stuff is going to become transparent. However, the destination stuff must already be opaque. If it's not opaque, the blending doesn't do what you want. This is why you have to provide an opaque UIColor and then do any transparency you want with the view directly.
I ran this myself, with these circumstances:
the window has a [UIColor greenColor] background
the fillColor is white
the punchedOutPath is a oval that's inset 10 points from the edges of the view.
the view has an alpha of 0.75
With the code above, I get this:
The interior is pure green, and the outside has the semi-transparent overlay.
Update
If your covering is an image, then you'll need to create a new image. But the principle is the same:
UIImage* ImageByPunchingPathOutOfImage(UIImage *image, UIBezierPath *path) {
UIGraphicsBeginImageContextWithOptions([image size], YES, [image scale]);
[image drawAtPoint:CGPointZero];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(ctx, kCGBlendModeDestinationOut);
[path fill];
UIImage *final = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return final;
}
You would then take the result of this function and put it into a UIImageView.
You can put this into a single screen app into the view controller: It will make a yellow background view and a blue layer on top of it that has an oval region cut out by a mask.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// create a yellow background
UIView *bg = [[UIView alloc] initWithFrame:self.view.bounds];
bg.backgroundColor = [UIColor yellowColor];
[self.view addSubview:bg];
// create the mask that will be applied to the layer on top of the
// yellow background
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.frame = self.view.frame;
// create the paths that define the mask
UIBezierPath *maskLayerPath = [UIBezierPath bezierPath];
[maskLayerPath appendPath:[UIBezierPath bezierPathWithRect:CGRectInset(self.view.bounds, 20, 20)]];
// here you can play around with paths :)
// [maskLayerPath appendPath:[UIBezierPath bezierPathWithOvalInRect:(CGRect){{80, 80}, {140, 190}}]];
[maskLayerPath appendPath:[UIBezierPath bezierPathWithOvalInRect:(CGRect){{100, 100}, {100, 150}}]];
maskLayer.path = maskLayerPath.CGPath;
// create the layer on top of the yellow background
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = self.view.layer.bounds;
imageLayer.backgroundColor = [[UIColor blueColor] CGColor];
// apply the mask to the layer
imageLayer.mask = maskLayer;
[self.view.layer addSublayer:imageLayer];
}
this might answer this question as well: UIBezierPath Subtract Path
I have two solution for you.
Draw this path on a CALayer. And use that CALayer as a mask layer for you actual CALayer.
Draw a rectangle with the sizes of you frame before adding arc.
UIBezierPath *path = [UIBezierPath bezierPathWithRect:view.frame];
[path addArcWithCenter:center
radius:200
startAngle:0
endAngle:2*M_PI
clockwise:YES];
I would use second solution. :)

Resources