Create UIView with four corner points? - objective-c

How can i create UIView with four corner.
0 = "NSPoint: {79.583483072916664, 54.148963562011716}"; // top left
1 = "NSPoint: {274.96375, 30.877176879882814}"; // top right
2 = "NSPoint: {306.15565104166666, 357.91370518493653}"; // bottom right
3 = "NSPoint: {77.101464843749994, 363.47374218750002}"; // bottom left
I have no idea where to start.

As your view is not a regular rectangle you need to first draw a path i.e. BezierPath and get a shape and imposing the shape on to the View. This view myView would behave like any other normal view
UIView *myView = [[UIView alloc]initWithFrame:self.view.frame];
UIBezierPath *linePath = [UIBezierPath bezierPath];
[linePath moveToPoint:CGPointMake(points[0].x, points[0].y)];
[linePath addLineToPoint:CGPointMake(points[1].x, points[1].y)];
[linePath addLineToPoint:CGPointMake(points[2].x, points[2].y)];
[linePath addLineToPoint:CGPointMake(points[3].x, points[3].y)];
[linePath closePath];
CAShapeLayer *shape = [[CAShapeLayer alloc]init];
[shape setPath:linePath.CGPath];
myView.layer.mask=shape;
myView.backgroundColor = [UIColor redColor];
[self.view addSubview:myView];
The output ScreenShot is as given below:

Your 4 corner points do not make a rectangle - a UIView cannot be an arbitrary quadrilateral.
For drawing irregular shapes you should look at Apple's docs for using bezier paths: https://developer.apple.com/library/content/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/BezierPaths/BezierPaths.html
So something like this:
- (void)drawRect:(CGRect)rect {
[[UIColor redColor] setStroke];
[[UIColor yellowColor] setFill];
UIBezierPath *linePath = [UIBezierPath bezierPath];
[linePath moveToPoint:CGPointMake(points[0].x, points[0].y)];
[linePath addLineToPoint:CGPointMake(points[1].x, points[1].y)];
[linePath addLineToPoint:CGPointMake(points[2].x, points[2].y)];
[linePath addLineToPoint:CGPointMake(points[3].x, points[3].y)];
[linePath setLineWidth:LINE_WIDTH];
[linePath closePath];
}

What you want is polygon shape and not rectangle. You can not assign polygon shape to view. but if you want to display only part of view fall between these points, you can draw UIBezierPath path with these points on view and then apply blend to clip outer part. here is sample code.
self.backgroundColor = [UIColor redColor];
UIBezierPath *aPath = [UIBezierPath bezierPath];
[aPath moveToPoint:CGPointMake( a.x, a.y)];
[aPath addLineToPoint:CGPointMake(b.x, b.y)];
[aPath addLineToPoint:CGPointMake(c.x, c.y)];
[aPath addLineToPoint:CGPointMake( d.x, d.y)];
[aPath closePath];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = aPath.CGPath;
[self.layer setMask:shapeLayer];

You can use CALayer for corners, Below lines create a view with corners rounded by 8.
UIView *myView = [[UIView alloc] init];
myView.layer.masksToBounds = YES;
myView.layer.cornerRadius = 8.0;
Or you can create view with certain frame like
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(x, y, w, h)];

Related

Rotate CAShapeLayer

I'm trying to rotate a CAShapeLayer in place
UIBezierPath *aPath = [UIBezierPath bezierPath];
CGFloat originx = (97.5+250);
CGFloat originy = (205+250);
[aPath moveToPoint:CGPointMake(originx,originy)];
[aPath addArcWithCenter:CGPointMake(originx+15, originy-30) radius:15 startAngle:DEGREES_TO_RADIANS(180) endAngle:DEGREES_TO_RADIANS(360) clockwise:YES];
[aPath addLineToPoint:CGPointMake(originx+30,originy)];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.anchorPoint = CGPointMake(originx, originy);
shapeLayer.path = [aPath CGPath];
shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
shapeLayer.lineWidth = 3.0;
shapeLayer.fillColor = [[UIColor blueColor] CGColor];
[aPath closePath];
shapeLayer.transform = CATransform3DMakeRotation(310 * M_PI/180, 0.0, 0.0, 1.0);
[self.view.layer addSublayer:shapeLayer];
I want to rotate the shape around an axis within the shape itself (the code above makes my little shape rotate around an axis several hundred pixels from the actual centre.
I felt this should be controlled with an anchorpoint like this one:
shapeLayer.anchorPoint = CGPointMake(originx, originy);
but it seems to do nothing within the code.
Any ideas?
The anchorPoint is relative to the bounds of the layer. You can't use the anchor point to place your layer. Instead you can place your shape relative to the zero point, rotate it, and the move it to the desired point. Rotation and translation are done by the transform.

How to create a custom Shaped UIView?

I want to create a UIView in Star, Pentagon, Hexagon, and Other custom Shapes.
I want to draw inside that custom shapes.
I've created a bezierPath and trying to mask the UIView to it.
but it doesn't work
CGFloat x=48;
CGFloat y=306;
oval2Path = UIBezierPath.bezierPath;
[oval2Path moveToPoint: CGPointMake(x+340, y+427.5)];
[oval2Path addLineToPoint: CGPointMake(x+476.95, y+523.2)];
[oval2Path addLineToPoint: CGPointMake(x+424.64, y+678.05)];
[oval2Path addLineToPoint: CGPointMake(x+255.36, y+678.05)];
[oval2Path addLineToPoint: CGPointMake(x+203.05, y+523.2)]; [oval2Path closePath];
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = oval2Path.CGPath;
FinalAlgView *myView=[[FinalAlgView alloc]initWithFrame:CGRectMake(0,0,800,800)];
myView.backgroundColor=[UIColor whiteColor];
myView.layer.mask = mask;
[self.view bringSubviewToFront:drawShape];
[self.view addSubview:myView];
IS there anyway to create UIView as custom Shapes

How to animate a drawn line in Objective C?

I am trying to animate drawn objects, for example I have drawn a line with this approach:
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(Center.x, Center.y)];
[path addLineToPoint:CGPointMake(Point.x,Point.y)];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [[UIColor whiteColor] CGColor];
shapeLayer.lineWidth = 1.5;
shapeLayer.fillColor = [[UIColor clearColor] CGColor];
[self.view.layer addSublayer:shapeLayer];
After a while I need to animate the line to have a different Point, but keeping Center as is, but I have no idea how to do that. Is my approach alright? Is there a better way to do so?
Solved it:
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:#"path"];
morph.duration = 1.0;
UIBezierPath *path5 = [UIBezierPath bezierPath];
[path5 moveToPoint:CGPointMake(P22.x+5, P22.y+5)];
[path5 addLineToPoint:CGPointMake(P21.x,P21.y)];
morph.fromValue = (id) path.CGPath;
morph.toValue = (id) path5.CGPath;
[shapeLayer addAnimation:morph forKey:nil];
//next line to update the shapeLayer's new path
shapeLayer.path = path5.CGPath;

NSTextFieldCell rounded corners stroke not rounded

I want to have an NSTextField with rounded corners, for that I subclassed my NSTextFieldCell, and used drawInteriorWithFrame:(NSRect) inView:(NSView *)
My code looks like that :
-(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
//Color Declarations
NSColor* fillColor = [NSColor colorWithCalibratedRed: 1 green: 1 blue: 1 alpha: 1];
NSColor* strokeColor = [NSColor colorWithCalibratedRed: 0.679 green: 0.679 blue: 0.679 alpha: 1];
//Shadow Declarations
NSShadow* shadow = [[NSShadow alloc] init];
[shadow setShadowColor: strokeColor];
[shadow setShadowOffset: NSMakeSize(0.1, 0.1)];
[shadow setShadowBlurRadius: 4];
//Rounded Rectangle Drawing
NSBezierPath* roundedRectanglePath = [NSBezierPath bezierPathWithRoundedRect:cellFrame xRadius: 10 yRadius: 10];
[fillColor setFill];
[roundedRectanglePath fill];
//Rounded Rectangle Inner Shadow
NSRect roundedRectangleBorderRect = NSInsetRect([roundedRectanglePath bounds], -shadow.shadowBlurRadius, -shadow.shadowBlurRadius);
roundedRectangleBorderRect = NSOffsetRect(roundedRectangleBorderRect, -shadow.shadowOffset.width, -shadow.shadowOffset.height);
roundedRectangleBorderRect = NSInsetRect(NSUnionRect(roundedRectangleBorderRect, [roundedRectanglePath bounds]), -1, -1);
NSBezierPath* roundedRectangleNegativePath = [NSBezierPath bezierPathWithRect: roundedRectangleBorderRect];
[roundedRectangleNegativePath appendBezierPath: roundedRectanglePath];
[roundedRectangleNegativePath setWindingRule: NSEvenOddWindingRule];
[NSGraphicsContext saveGraphicsState];
{
NSShadow* shadowWithOffset = [shadow copy];
CGFloat xOffset = shadowWithOffset.shadowOffset.width + round(roundedRectangleBorderRect.size.width);
CGFloat yOffset = shadowWithOffset.shadowOffset.height;
shadowWithOffset.shadowOffset = NSMakeSize(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset));
[shadowWithOffset set];
[[NSColor grayColor] setFill];
[roundedRectanglePath addClip];
NSAffineTransform* transform = [NSAffineTransform transform];
[transform translateXBy: -round(roundedRectangleBorderRect.size.width) yBy: 0];
[[transform transformBezierPath: roundedRectangleNegativePath] fill];
}
[NSGraphicsContext restoreGraphicsState];
[strokeColor setStroke];
[roundedRectanglePath setLineWidth: 2];
[roundedRectanglePath stroke];
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
The result looks great apart from borders which are not rounded. Images are better than words : My NSTextField
All help is accepted ! :D Thank you in advance.
UPDATE
I did a copy paste with the code of the site you said, but I'm still having the same trouble...
NSTextField with on rounded corner
You may simply choose the Border of Text Field in its Attributes Inspector if you want a text field with its all corners rounded. Or to draw a text field with any one round corner, go through this
Also if you want draw a custom all round corner text field, just follow the steps in above link, but instead of drawing a bezier path with one round corner, simply draw a bezier path with all corners round using
[NSBezierPath bezierPathWithRoundedRect:textfieldrect xRadius:10 yRadius:10]

Gap between clipping regions

I'm drawing a lot of trapezoids filled with a NSGradient. I expect them to join seamlessly, which is not the case. In this case, I would prefer to use clipping instead of explicitly filling a NSBezierPath with a gradient.
Here is the incriminated code:
-(void)drawRect:(NSRect)dirtyRect {
[[NSColor blackColor]set];
NSRectFill(self.bounds);
NSPoint v0 = NSMakePoint(100,100);
NSPoint v1 = NSMakePoint(200,100);
NSPoint v2 = NSMakePoint(100,200);
NSPoint v3 = NSMakePoint(200,200);
NSPoint v4 = NSMakePoint(150, 150);
NSBezierPath * triangle0 = [NSBezierPath bezierPath];
[triangle0 moveToPoint:v0];
[triangle0 lineToPoint:v1];
[triangle0 lineToPoint:v2];
[triangle0 closePath];
NSBezierPath * triangle1 = [NSBezierPath bezierPath];
[triangle1 moveToPoint:v1];
[triangle1 lineToPoint:v2];
[triangle1 lineToPoint:v3];
[triangle1 closePath];
NSGradient * gradient = [[NSGradient alloc] initWithColorsAndLocations:[NSColor whiteColor],0.0,[NSColor orangeColor],1.0,nil];
[NSGraphicsContext saveGraphicsState];
[triangle0 addClip];
[gradient drawFromPoint:v0 toPoint:v4 options:NSGradientDrawsAfterEndingLocation|NSGradientDrawsBeforeStartingLocation];
// [gradient drawFromPoint:v0 toPoint:v4 options:0];
[NSGraphicsContext restoreGraphicsState];
[NSGraphicsContext saveGraphicsState];
[triangle1 addClip];
[gradient drawFromPoint:v3 toPoint:v4 options:NSGradientDrawsAfterEndingLocation|NSGradientDrawsBeforeStartingLocation];
//[gradient drawFromPoint:v3 toPoint:v4 options:0];
[NSGraphicsContext restoreGraphicsState];
What should I do to avoid the gap between the two triangles ?
Edit:
Here is an attempt to go around this problem by explicitely filling NSBezierPath.
-(void)drawRect:(NSRect)dirtyRect {
[[NSColor blackColor]set];
NSRectFill([self bounds]);
CGContextRef c = [[NSGraphicsContext currentContext] graphicsPort];
CGContextTranslateCTM(c, 0.5, 0.5);
NSPoint v0 = NSMakePoint(100,100);
NSPoint v1 = NSMakePoint(200,100);
NSPoint v2 = NSMakePoint(100,200);
NSPoint v3 = NSMakePoint(200,200);
NSBezierPath * triangle0 = [NSBezierPath bezierPath];
[triangle0 moveToPoint:v0];
[triangle0 lineToPoint:v1];
[triangle0 lineToPoint:v2];
[triangle0 closePath];
NSBezierPath * triangle1 = [NSBezierPath bezierPath];
[triangle1 moveToPoint:v1];
[triangle1 lineToPoint:v2];
[triangle1 lineToPoint:v3];
[triangle1 closePath];
[[NSColor whiteColor]set];
NSGradient * gradient = [[NSGradient alloc] initWithColorsAndLocations:[NSColor whiteColor],0.0,[NSColor orangeColor],1.0,nil];
[gradient drawInBezierPath:triangle0 angle:45];
[gradient drawInBezierPath:triangle1 angle:45+180];
NSBezierPath * seam = [ NSBezierPath bezierPath]; [seam moveToPoint:v1]; [seam lineToPoint:v2];
[[NSColor orangeColor]set]; [seam stroke];
NSGradient * gradient2 = [[NSGradient alloc] initWithColorsAndLocations:[NSColor whiteColor],0.0,[NSColor orangeColor],0.5,[NSColor whiteColor],1.0,nil];
NSBezierPath * squarePath = [NSBezierPath bezierPath];
[squarePath moveToPoint:v0];
[squarePath lineToPoint:v1];
[squarePath lineToPoint:v3];
[squarePath lineToPoint:v2];
[squarePath closePath];
CGContextTranslateCTM(c, 200, 0);
[gradient2 drawInBezierPath:squarePath angle:45];
}
On the left, two triangles filled with the same NSGradient.
On the right, a square filled with an NSGradient, the goal I'm trying to reach.
The seam is an attempt to hide the black line between the two NSBezierPath, but it doesn't really work. In this case, it is barely visible, but it will be visible if I draw a large amount of such triangles.
Edit for Rengers answer:
Here is how it looks without antialiasing:
Technically, this is the good answer, but now the problem is that, unsurprisingly, the edge of the polygon are not longer antialiased. Any idea how I could remove the gap between the clipping regions, without losing the antialiasing on the edges ?
This is probably because of anti-aliasing which is enabled by default. You can disable it using:
[[NSGraphicsContext currentContext] setShouldAntialias:NO];