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];
Related
I have a square UIView which i need to divide into cross section with two different colors as shown
here is my code to achieve that graph:
//Define the drawing path
UIBezierPath *path1 = [[UIBezierPath alloc] init];
//path Move to start drawing position
[path1 moveToPoint:CGPointMake(200, 100)];
//Draw a straight line from the starting position to(100, 200)
[path1 addLineToPoint:CGPointMake(100, 100)];
//To draw a line from (100, 200) to (200, 200)
[path1 addLineToPoint:CGPointMake(100, 200)];
//close path
[path1 closePath];
CAShapeLayer *layer1 = [[CAShapeLayer alloc] init];
layer1.path = path1.CGPath;
layer1.fillColor = [UIColor colorWithRed:0.88 green:0.87 blue:0.87 alpha:1.0].CGColor;
[self.view.layer addSublayer:layer1];
UIBezierPath *path2 = [[UIBezierPath alloc] init];
[path2 moveToPoint:CGPointMake(200, 100)];
[path2 addLineToPoint:CGPointMake(200, 200)];
[path2 addLineToPoint:CGPointMake(100, 200)];
[path2 closePath];
CAShapeLayer *layer2 = [[CAShapeLayer alloc] init];
layer2.path = path2.CGPath;
layer2.fillColor = [UIColor colorWithRed:0.89 green:0.57 blue:0.53 alpha:1.0].CGColor;
[self.view.layer addSublayer:layer2];
Here is the result:
result image
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.
how can I create a label below like this image in iOS (Objective-c)?
My problem is not all corners, because I can round top-left & bottom-right corners by writing codes like this:
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.customView.bounds
byRoundingCorners:UIRectCornerBottomRight | UIRectCornerTopLeft
cornerRadii:(CGSize){7.0, 7.0}].CGPath;
self.customLabel.layer.mask = maskLayer;
and the result is this:
Now how can I write codes for top-right & bottom-left to reach the goal?
Update:
I didn't use the height as a variable in my equations it may affect the results.
Better results
- (void)drawRect:(CGRect)rect
{
CGFloat curveWidth = 40;
CGFloat width = self.frame.size.width;
CGFloat height = self.frame.size.height;
// UIEdgeInsets insets = {0, curveWidth, 0, curveWidth};
// [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
UIBezierPath *path = [UIBezierPath bezierPath];
UIColor *fillColor = [UIColor orangeColor];
[fillColor setFill];
[path moveToPoint:CGPointMake(0, height)];
[path addCurveToPoint:CGPointMake(curveWidth*2, 0) controlPoint1:CGPointMake(curveWidth*0.75, height) controlPoint2:CGPointMake(curveWidth*0.25, 0)];
[path addLineToPoint:CGPointMake(width, 0)];
[path addCurveToPoint:CGPointMake(width-curveWidth*2, height) controlPoint1:CGPointMake(width-curveWidth*0.75, 0) controlPoint2:CGPointMake(width-curveWidth*0.25, height)];
[path closePath];
[path fill];
[super drawRect: rect];
}
Original Answer:
I wrote the code for you. follow the steps:
Create a custom UILabel by subclassing it. (code below)
Add a UILabel to your view using interface builder.
Add your constraints.
In the Identifier inspector tab, change class to your.
--
#implementation CurvedLabel
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGFloat curveWidth = 30;
CGFloat width = self.frame.size.width;
CGFloat height = self.frame.size.height;
// UIEdgeInsets insets = {0, curveWidth, 0, curveWidth};
// [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
UIBezierPath *path = [UIBezierPath bezierPath];
UIColor *fillColor = [UIColor orangeColor];
[fillColor setFill];
[path moveToPoint:CGPointMake(0, height)];
[path addCurveToPoint:CGPointMake(curveWidth*2, 0) controlPoint1:CGPointMake(curveWidth, height) controlPoint2:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(width, 0)];
[path addCurveToPoint:CGPointMake(width-curveWidth*2, height) controlPoint1:CGPointMake(width-curveWidth, 0) controlPoint2:CGPointMake(width, height)];
[path closePath];
[path fill];
[super drawRect: rect];
}
#end
I don't believe there is any simple way of doing that, you could use a png file, if that is not a option you should look in to UIBezierPath, check this link out: ios drawing concepts
Sure you can do it with a UIBezierPath.
I have made a pretty simple setup you can use.
// This is the parallelogram's corner structure
// 1------2
// / /
// / /
// 4------3
int xOffset = 150; // x Offset on the parent view - here self.view
int yOffset = 50; // y Offset on the parent view - here self.view
int flatten = 50; // The difference from center of curve to spike point (try to adjust it and see what it does)
int width = 200;
int height = 100;
UIBezierPath * maskLayer = [UIBezierPath bezierPath];
// Start at corner 1
[maskLayer moveToPoint: CGPointMake(xOffset+flatten, yOffset)];
// Go to corner 2
[maskLayer addLineToPoint: CGPointMake(xOffset+width+flatten, yOffset)];
// Curve it down to corner 3
[maskLayer addCurveToPoint: CGPointMake(xOffset+width-flatten, height+yOffset) controlPoint1: CGPointMake(xOffset+width, yOffset) controlPoint2:CGPointMake(xOffset+width, height+yOffset)];
// Go straight to corner 4
[maskLayer addLineToPoint: CGPointMake(xOffset, height+yOffset)];
// Curve it back to corner 1
[maskLayer addCurveToPoint: CGPointMake(xOffset+(2*flatten), yOffset) controlPoint1:CGPointMake(xOffset+flatten, yOffset+height) controlPoint2: CGPointMake(xOffset+flatten, yOffset)];
// Create the shape and fill it with a color
CAShapeLayer *maskShapeLayer = [CAShapeLayer layer];
maskShapeLayer.path = maskLayer.CGPath;
maskShapeLayer.fillColor = [UIColor redColor].CGColor;
// add the layer to a view's layer (here self.view.layer)
[self.view.layer addSublayer:maskShapeLayer];
If you wanna make the two ´spikes´ more rounded, you can make the y-value in the addCurveToPoint points closer to the parallelogram's y-center-axis (height/2).
You will have to useaddCurveToPoint of UIBezierPath to make it curvy by giving it controlPoint1 and controlPoint2
For reference, you can look at Apple's Documentation
Also, this is a helpful reference too.
You will have to play around with UIBezierPath's properties
Before the thought of posting this question came into my mind I had already spent a few hours on this small snippet of code... So I wrote two different pieces of code in two different classes, and both were used to cut out a specific shape. But for some reason only one of them worked!?!? I have ABSOLUTELY no idea why this happened, for they have the exact same structure.
Below is the snippet that WORKS.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor blackColor];
UIBezierPath *arrowPath = [UIBezierPath bezierPath];
CGPoint start = CGPointMake(self.frame.size.width - 2, self.frame.size.height / 2);
[arrowPath moveToPoint:start];
[arrowPath addLineToPoint:CGPointMake(7, 7)];
[arrowPath addLineToPoint:CGPointMake(2, 7)];
[arrowPath addLineToPoint:CGPointMake(self.frame.size.width - 7, self.frame.size.height/2)];
[arrowPath addLineToPoint:CGPointMake( 2, self.frame.size.height - 7)];
[arrowPath addLineToPoint:CGPointMake(7, self.frame.size.height - 7)];
[arrowPath addLineToPoint:start];
[arrowPath closePath];
UIView *arrow = [[UIView alloc]initWithFrame:self.frame];
arrow.backgroundColor = [UIColor whiteColor];
CAShapeLayer *mask = [CAShapeLayer layer];
mask.frame = self.frame;
mask.backgroundColor = (__bridge CGColorRef)([UIColor clearColor]);
mask.path = arrowPath.CGPath;
arrow.layer.mask = mask;
[self addSubview:arrow];
}
return self;
}
And here is the piece of code that only works PARTIALLY.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
UIView *roundedRectView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height - 20)];
roundedRectView.backgroundColor = [UIColor clearColor];
[self addSubview:roundedRectView];
UIBezierPath *roundedRectPath = [UIBezierPath bezierPathWithRoundedRect:roundedRectView.frame cornerRadius:10];
UIView *rectangle = [[UIView alloc]initWithFrame:roundedRectView.frame];
rectangle.backgroundColor = [UIColor blackColor];
CAShapeLayer *mask = [CAShapeLayer layer];
mask.frame = rectangle.frame;
mask.path = roundedRectPath.CGPath;
mask.backgroundColor = (__bridge CGColorRef)([UIColor clearColor]);
rectangle.layer.mask = mask;
rectangle.alpha = 0.8;
[roundedRectView addSubview:rectangle];
UIView *rectangleTwo = [[UIView alloc]initWithFrame:CGRectMake(0, self.frame.size.height - 20, self.frame.size.width, 20)];
rectangleTwo.backgroundColor = [UIColor redColor];
UIBezierPath *trianglePath = [UIBezierPath bezierPath];
CGPoint start = CGPointMake(rectangleTwo.frame.size.width / 2 - 15, 0);
[trianglePath moveToPoint:start];
[trianglePath addLineToPoint:CGPointMake(start.x + 20, start.y)];
[trianglePath addLineToPoint:CGPointMake(start.x + 10, start.y + 20)];
[trianglePath addLineToPoint:start];
[trianglePath closePath];
CAShapeLayer *triangleMask = [CAShapeLayer layer];
triangleMask.frame = rectangleTwo.frame;
triangleMask.path = trianglePath.CGPath;
rectangleTwo.layer.mask = triangleMask;
[self addSubview:rectangleTwo];
}
return self;
}
Allocating this speech bubble using CGRectMake(0,0,80,60), only the rounded rectangle showed up on the screen, but the triangle was no where to be seen.
Can anyone point out what's wrong with this code? Any help is appreciated, thanks!!
Problem solved!
Turned out that my triangleMask.frame wasn't defined properly. Instead of triangleMask.path = rectangleTwo.frame I should've used triangleMask.frame = CGRectMake(0, 0, rectangleTwo.frame.size.width, rectangleTwo.frame.size.height); instead! CAShapeLayer's origin is relative to that of the shape on which you want to place it!
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.