BezierPath and masking - ios

I want to set up UIBezierPath as mask into my view:
The goal is something like this:
So i draw the View with frame:
CGFloat round = 80;
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(self.frame.size.width/2-105, 0, 210, 60+round)];
myView.backgroundColor = [UIColor redColor];
And then try to add BezierMask:
UIBezierPath *aPath = [UIBezierPath bezierPath];
CGSize viewSize = CGSizeMake(myView.frame.size.width, myView.frame.size.height); //(210,80)
CGPoint startPoint = CGPointMake(myView.frame.origin.x,myView.frame.origin.y); //(279,0)
[aPath moveToPoint:startPoint]; //(279,0)
[aPath addLineToPoint:CGPointMake(startPoint.x+viewSize.width,startPoint.y)]; //(489,0)
[aPath addLineToPoint:CGPointMake(startPoint.x+viewSize.width,startPoint.y+viewSize.height-round)]; //(489,60)
[aPath addQuadCurveToPoint:CGPointMake(startPoint.x,startPoint.y+viewSize.height-round) controlPoint:CGPointMake(startPoint.x+(viewSize.width/2),80)]; //(279,60) : (384,80)
[aPath closePath];
CAShapeLayer *layer = [CAShapeLayer layer];
layer.frame = myView.bounds;
layer.path = aPath.CGPath;
myView.layer.mask = layer;
[self addSubview:myView];
But my view is disappearing after that. Why?

I guess you are using improper coordinations for your mask layer. Your mask layer must be positioned within the view which it masks, not in the view's superview.
Try these changes:
CGSize viewSize = myView.bounds.size;
CGPoint startPoint = CGPointZero;
Your code with those little modifications works OK for me.

Related

How to get CAShapeLayer's frame confront to its superview?

Can anybody help me to get frame of CAShapeLayer which is been added in UIImageView as a SubLayer?
Following is my code for the same:
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.fillColor = [[UIColor redColor] colorWithAlphaComponent:0.15].CGColor;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.lineWidth = 3.0;
shapeLayer.zPosition = 1;
[_imgBackground.layer insertSublayer:shapeLayer atIndex:1];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 50)]; // left top point of rack
[path addLineToPoint:CGPointMake(100, 50)]; // right top point of rack
[path addLineToPoint:CGPointMake(100, 100)]; // right bottom point of rack
[path addLineToPoint:CGPointMake(50, 100)]; // left bottom point of rack
[path closePath]; // closing rectangle/path
shapeLayer.path = path.CGPath;
As I know we can get frame from the following line:
CGRect bounds = CGPathGetBoundingBox(shapeLayer.path);
But it only returns frame according to self.view
I want its frame according to UIImageView.
Thanks in advance.
Well I got the answer from James's comment. Following line saved my day:
CGRect bounds = [self.view convertRect:CGPathGetBoundingBox(shapeLayer.path) toView:_imgBackground];
from this reference.

Drawing bezierPath

I was tinkering around with BezierPath and noticed something that I can't seem to figure out. This is the code-
UIBezierPath *path = [UIBezierPath new];
CGSize screenDimensions = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
CGPoint firstPoint = CGPointMake(screenDimensions.width/2, screenDimensions.height/2);
CGPoint secondPoint = CGPointMake(screenDimensions.width/2 + 10, screenDimensions.height/2 + 10);
CGPoint thirdPoint = CGPointMake(screenDimensions.width/2 - 10, screenDimensions.height/2 + 10);
[path moveToPoint:firstPoint];
[path addLineToPoint:secondPoint];
[path addLineToPoint:thirdPoint];
[path addLineToPoint:firstPoint];
[path closePath];
CAShapeLayer *tempLayer = [CAShapeLayer new];
[tempLayer setPath:path.CGPath];
UIView *tempView = [[UIView alloc] initWithFrame:CGRectMake(screenDimensions.width/2 - 30, screenDimensions.height/2 - 30, 100, 100)];
// UIView *tempView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
[tempView setBackgroundColor:[UIColor blueColor]];
tempView.layer.mask = tempLayer;
[self.view addSubview:tempView];
If i run the above code block, nothing is drawn on the UIView that is added on the screen. But if i were to comment the current "tempView" and uncomment the currently commented allocation, it would draw on screen perfectly. Can anyone please point out what am I doing wrong here when setting the frame or is it something else?
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint point1 = CGPointMake(100, 100);
CGPoint point2 = CGPointMake(200, 200);
CGPoint point3 = CGPointMake(300, 300);
[path moveToPoint:point1];
[path addQuadCurveToPoint:point3 controlPoint:point2];
CAShapeLayer *shape = [CAShapeLayer layer];
shape.path = path.CGPath;
shape.lineWidth = 5;
shape.strokeColor = [UIColor blueColor].CGColor;
shape.fillColor = [UIColor clearColor].CGColor;
shape.frame = self.view.bounds;
[self.view.layer addSublayer:shape];
You can see below output :
It's a simple problem.
Let's say the screen dimensions are 320x480 (and old iPhone). This means that firstPoint will be 160, 240.
In the current code, your view's frame is 130, 210, 100, 100. It's width and height are both 100.
The shape layer will be drawn relative to the view's bounds, not its frame. So as far as the layer and bezier path are concerned, the view's bounds are 0, 0, 100, 100. Since firstPoint is outside those bounds, it doesn't appear. It's actually drawn but outside the visible bounds of the view.
When you switch the two lines that create the view, the view's frame becomes 0, 0, 320, 480. This means its bounds is also 0, 0, 320, 480. The view is much larger now and the layer and bezier path fit and can be seen.
The proper solution is to create the coordinates of the bezier path based on the size of the view it will be applied to, not the size of the screen. This way, the bezier path will fit inside its view no matter how big it is.
More like this:
CGSize screenDimensions = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
UIView *tempView = [[UIView alloc] initWithFrame:CGRectMake(screenDimensions.width/2 - 30, screenDimensions.height/2 - 30, 100, 100)];
// UIView *tempView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIBezierPath *path = [UIBezierPath new];
CGPoint firstPoint = CGPointMake(tempView.bounds.size.width/2, tempView.bounds.size.height/2);
CGPoint secondPoint = CGPointMake(tempView.bounds.size.width/2 + 10, tempView.bounds.size.height/2 + 10);
CGPoint thirdPoint = CGPointMake(tempView.bounds.size.width/2 - 10, tempView.bounds.size.height/2 + 10);
[path moveToPoint:firstPoint];
[path addLineToPoint:secondPoint];
[path addLineToPoint:thirdPoint];
[path addLineToPoint:firstPoint];
[path closePath];
CAShapeLayer *tempLayer = [CAShapeLayer new];
[tempLayer setPath:path.CGPath];
[tempView setBackgroundColor:[UIColor blueColor]];
tempView.layer.mask = tempLayer;
[self.view addSubview:tempView];

How can i create curve UIView/UIImageView not with animation?

How can i create an uiview which looks like this and also its subview get the same effect.
After searching in web i found it can be done by CALayer, but i do not have enough knowledge on this . Please help me. Thanks in advance.
Upto my levels and my understanding I have done some coding that may helpful to you..
If not please ignore it..
1st Create a custom View from new file
Next, we need to change the class of the ViewController's view property from UIView to our CustomView class. Open MainStoryboard.storyboard and in the document outline click on View icon. Then, open the identity inspector (option+command+3) and change to class name to CustomView.
then add this code in custom View
- (void)drawRect:(CGRect)rect
{
// Drawing code
UIImage *imageRecipe =[UIImage imageNamed:#"statement_card_1.png"];
UIImageView *imgView = [[UIImageView alloc]initWithFrame:CGRectMake(0 ,0 ,290, 200)];
imgView.image = imageRecipe;
[self addSubview:imgView];
CGPoint secondPoint = CGPointZero;
CGPoint point1 = CGPointZero;
CGPoint initial = CGPointZero;
UIBezierPath *aPath = [UIBezierPath bezierPath];
initial = CGPointMake(30, 60);
[aPath moveToPoint:initial];
CGPoint point = CGPointMake(290, 60);
CGPoint centerPoint = CGPointMake(point.x/2, -10);
[aPath addQuadCurveToPoint:point controlPoint:centerPoint];
secondPoint = CGPathGetCurrentPoint(aPath.CGPath);
[aPath addLineToPoint:CGPointMake(secondPoint.x, 200.0)];
point1 = CGPathGetCurrentPoint(aPath.CGPath);
CGPoint p = CGPointMake(initial.x,point1.y);
CGPoint cp = CGPointMake(point1.x/2 ,point1.y/2 + 30);
[aPath addQuadCurveToPoint:p controlPoint:cp];
[aPath addLineToPoint:CGPointMake(initial.x, secondPoint.y)];
[aPath closePath];
[aPath stroke];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imgView.bounds;
maskLayer.path = aPath.CGPath;
imgView.layer.mask = maskLayer;
}
won't happen without OpenGL or some other engine. you can't morph a CALayer that way. The basic idea of a layer is that it is a plane.
maybe fake it with an image and some rotated views or something?

CAShapeLayer's frame and bounds properties are 0

CGPoint startPoint = CGPointMake(50, 50);
CGPoint endPoint = CGPointMake(100, 100);
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:startPoint];
[bezierPath addLineToPoint:endPoint];
layer = [CAShapeLayer layer];
//layer.frame = CGRectMake(50, 50, 100, 100);
layer.backgroundColor = [UIColor redColor].CGColor;
layer.path = bezierPath.CGPath;
layer.strokeColor = [UIColor greenColor].CGColor;
layer.fillColor = nil;
[self.view.layer addSublayer:layer];
NSLog(#"%#",NSStringFromCGRect(layer.bounds));
NSLog(#"%#",NSStringFromCGRect(layer.frame));
The Log Message that i get is 0,0 for both frame and bound yet the layer is drawn on the Screen.
I am confused on how it is possible that a layer can be drawn on screen in spite of having 0 on both bounds and frames. I though every view or layer needed to have these properties set in order to be displayed on screen.

Why does a CAShapeLayer's stroke extend out of the frame?

Here's sample code:
//Called by VC:
HICircleView *circleView = [[HICircleView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
// init of circle view
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
CAShapeLayer *borderLayer = [CAShapeLayer layer];
borderLayer.fillColor = [UIColor whiteColor].CGColor;
borderLayer.path = [UIBezierPath bezierPathWithOvalInRect:self.frame].CGPath;
borderLayer.strokeColor = [[UIColor redColor] CGColor];
borderLayer.lineWidth = 5;
[self.layer addSublayer:borderLayer];
}
return self;
}
OK, thanks for the answer. to shift i:
CGRect rect = CGRectMake(3, 3, self.frame.size.width, self.frame.size.height);
borderLayer.path = [UIBezierPath bezierPathWithOvalInRect:rect].CGPath;
And made 6 the line width.
Setting the lineWidth draws a line, where the actual path is exactly in the middle of the drawn line.
If you want the drawn line to line up with something, you will have to shift the path by half the lineWidth.
You can shift the path by using - (void)applyTransform:(CGAffineTransform)transform on UIBezierPath and apply a translate transform.
If you want a drawn path to be contained in a certain area, shifting the path doesn't help. In that case just create a smaller path. If you want to draw a 100ptx100pt rect with a line width of 5, you have to draw a path in a 95pt*95pt rect (2.5pt space on either side).
You'd rather choose your view's bounds property for calculation. Frame property won't work properly if it's origin is greater than (0,0). You can use CGRectInsets to adjust your circle's rectangle instead of performing transform calculations. This will automatically position the rectangle centered within the original rectangle.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
CAShapeLayer *borderLayer = [CAShapeLayer layer];
borderLayer.fillColor = [UIColor whiteColor].CGColor;
CGFloat lineWidth = 5;
CGRect rect = CGRectInset(self.bounds, lineWidth / 2, lineWidth / 2);
borderLayer.path = [UIBezierPath bezierPathWithOvalInRect:rect].CGPath;
borderLayer.strokeColor = [[UIColor redColor] CGColor];
borderLayer.lineWidth = lineWidth;
[self.layer addSublayer:borderLayer];
}
return self;
}

Resources