Swift 3: Hexagon do not show center of the imageView - ios

My hexagon do not show in center of the imageView, how to fix this? Here is my code.
func roundedPolygonPath(rect: CGRect, lineWidth: CGFloat, sides: NSInteger, cornerRadius: CGFloat, rotationOffset: CGFloat = 0)
-> UIBezierPath {
let path = UIBezierPath()
let theta: CGFloat = CGFloat(2.0 * M_PI) / CGFloat(sides) // How much to turn at every corner
// let offset: CGFloat = cornerRadius * tan(theta / 2.0) // Offset from which to start rounding corners
let width = min(rect.size.width, rect.size.height) // Width of the square
let center = CGPoint(x: rect.origin.x + width + 10 / 2.0, y: rect.origin.y + width / 2.0)
// Radius of the circle that encircles the polygon
// Notice that the radius is adjusted for the corners, that way the largest outer
// dimension of the resulting shape is always exactly the width - linewidth
let radius = (width - lineWidth + cornerRadius - (cos(theta) * cornerRadius)) / 2.0
// Start drawing at a point, which by default is at the right hand edge
// but can be offset
var angle = CGFloat(rotationOffset)
let corner = CGPoint(center.x + (radius - cornerRadius) * cos(angle), center.y + (radius - cornerRadius) * sin(angle))
path.move(to: CGPoint(corner.x + cornerRadius * cos(angle + theta), corner.y + cornerRadius * sin(angle + theta)))
for _ in 0 ..< sides {
angle += theta
let corner = CGPoint(center.x + (radius - cornerRadius) * cos(angle), center.y + (radius - cornerRadius) * sin(angle))
let tip = CGPoint(center.x + radius * cos(angle), center.y + radius * sin(angle))
let start = CGPoint(corner.x + cornerRadius * cos(angle - theta), corner.y + cornerRadius * sin(angle - theta))
let end = CGPoint(corner.x + cornerRadius * cos(angle + theta), corner.y + cornerRadius * sin(angle + theta))
path.addLine(to: start)
path.addQuadCurve(to: end, controlPoint: tip)
}
path.close()
// Move the path to the correct origins
let bounds = path.bounds
let transform = CGAffineTransform(translationX: -bounds.origin.x + rect.origin.x + lineWidth / 2.0,
y: -bounds.origin.y + rect.origin.y + lineWidth / 2.0)
path.apply(transform)
return path
}

Both of this elements has x and y equals 0. Try to use width properties to calculate transform.

Related

Create hexagon design using UIBezierPath in swift ios

I want to achieve a hexagon shape using UIBezierPath like in the image below. But I don´t know how to create this kind of hexagon shape.
I´ve tried following code:
func roundedPolygonPath(rect: CGRect, lineWidth: CGFloat, sides: NSInteger, cornerRadius: CGFloat, rotationOffset: CGFloat = 0)
-> UIBezierPath {
let path = UIBezierPath()
let theta: CGFloat = CGFloat(2.0 * M_PI) / CGFloat(sides) // How much to turn at every corner
let offset: CGFloat = cornerRadius * tan(theta / 2.0) // Offset from which to start rounding corners
let width = min(rect.size.width, rect.size.height) // Width of the square
let center = CGPoint(x: rect.origin.x + width / 2.0, y: rect.origin.y + width / 2.0)
// Radius of the circle that encircles the polygon
// Notice that the radius is adjusted for the corners, that way the largest outer
// dimension of the resulting shape is always exactly the width - linewidth
let radius = (width - lineWidth + cornerRadius - (cos(theta) * cornerRadius)) / 2.0
// Start drawing at a point, which by default is at the right hand edge
// but can be offset
var angle = CGFloat(rotationOffset)
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle))
path.move(to: CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta)))
for _ in 0 ..< sides {
angle += theta
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle))
let tip = CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle))
let start = CGPoint(x: corner.x + cornerRadius * cos(angle - theta), y: corner.y + cornerRadius * sin(angle - theta))
let end = CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta))
path.addLine(to: start)
path.addQuadCurve(to: end, controlPoint: tip)
}
path.close()
// Move the path to the correct origins
let bounds = path.bounds
let transform = CGAffineTransform(translationX: -bounds.origin.x + rect.origin.x + lineWidth / 2.0,
y: -bounds.origin.y + rect.origin.y + lineWidth / 2.0)
path.apply(transform)
return path
}
Current code output:
for this kind of shape i took two center points and make rectangle hexagon from that
func customPolygonPath(rect: CGRect, lineWidth: CGFloat, sides: NSInteger, cornerRadius: CGFloat, rotationOffset: CGFloat = 0)
-> UIBezierPath {
let path = UIBezierPath()
let theta: CGFloat = CGFloat(2.0 * M_PI) / CGFloat(sides) // How much to turn at every corner
let offset: CGFloat = cornerRadius * tan(theta / 2.0) // Offset from which to start rounding corners
let width = min(rect.size.width, rect.size.height) // Width of the square
let height = max(rect.size.width, rect.size.height)
let upperCenter = CGPoint(x: rect.origin.x + width / 2.0, y: rect.origin.y + width / 2.0)
let bottomCenter = CGPoint(x: upperCenter.x, y: height - upperCenter.y)
let center = CGPoint(x: rect.origin.x + width / 2.0, y: rect.origin.y + height / 2.0)
// Radius of the circle that encircles the polygon
// Notice that the radius is adjusted for the corners, that way the largest outer
// dimension of the resulting shape is always exactly the width - linewidth
let radius = (width - lineWidth + cornerRadius - (cos(theta) * cornerRadius)) / 2.0
// Start drawing at a point, which by default is at the right hand edge
// but can be offset
var angle = CGFloat(rotationOffset)
let bottomCorner = CGPoint(x: bottomCenter.x + (radius - cornerRadius) * cos(angle), y: bottomCenter.y + (radius - cornerRadius) * sin(angle))
path.move(to: CGPoint(x: bottomCorner.x + cornerRadius * cos(angle + theta), y: bottomCorner.y + cornerRadius * sin(angle + theta)))
// print("corner:::\(corner)")
// print("point:::\(CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta)))")
for i in 0 ..< sides {
angle += theta
if i == 0 || i == 4 || i == 5 {
//bottomCenter
let corner = CGPoint(x: bottomCenter.x + (radius - cornerRadius) * cos(angle), y: bottomCenter.y + (radius - cornerRadius) * sin(angle))
let tip = CGPoint(x: bottomCenter.x + radius * cos(angle), y: bottomCenter.y + radius * sin(angle))
let start = CGPoint(x: corner.x + (cornerRadius * cos(angle - theta)), y: corner.y + (cornerRadius * sin(angle - theta)))
let end = CGPoint(x: corner.x + (cornerRadius * cos(angle + theta)), y: corner.y + (cornerRadius * sin(angle + theta)))
path.addLine(to: start)
path.addQuadCurve(to: end, controlPoint: tip)
}else {
//upperCenter
let corner = CGPoint(x: upperCenter.x + (radius - cornerRadius) * cos(angle), y: upperCenter.y + (radius - cornerRadius) * sin(angle))
let tip = CGPoint(x: upperCenter.x + radius * cos(angle), y: upperCenter.y + radius * sin(angle))
let start = CGPoint(x: corner.x + cornerRadius * cos(angle - theta), y: corner.y + cornerRadius * sin(angle - theta))
let end = CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta))
path.addLine(to: start)
path.addQuadCurve(to: end, controlPoint: tip)
}
}
path.close()
// Move the path to the correct origins
let bounds = path.bounds
let transform = CGAffineTransform(translationX: 0,
y: 0)
path.apply(transform)
return path
}

How to draw another layer of hexagon UIBezier Path and animate accordingly

I'm looking for a way to add another layer of hexagon bezier path like the ones below.
I have been able to create Hexagon using bezier path and animate accordingly but I am trying to add another grey colour layer of bezier path. I tried adding multiple bezier paths but it doesn't work.
This is the output I achieved.
Here is my LoaderView class
class LoaderView: UIView {
private let lineWidth : CGFloat = 5
internal var backgroundMask = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
setUpLayers()
createAnimation()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpLayers()
createAnimation()
}
func setUpLayers()
{
backgroundMask.lineWidth = lineWidth
backgroundMask.fillColor = nil
backgroundMask.strokeColor = UIColor.blue.cgColor
layer.mask = backgroundMask
layer.addSublayer(backgroundMask)
}
func createAnimation()
{
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.duration = 1
animation.repeatCount = .infinity
backgroundMask.add(animation, forKey: "MyAnimation")
}
override func draw(_ rect: CGRect) {
let sides = 6
let rect = self.bounds
let path = UIBezierPath()
let cornerRadius : CGFloat = 10
let rotationOffset = CGFloat(.pi / 2.0)
let theta: CGFloat = CGFloat(2.0 * .pi) / CGFloat(sides) // How much to turn at every corner
let width = min(rect.size.width, rect.size.height) // Width of the square
let center = CGPoint(x: rect.origin.x + width / 2.0, y: rect.origin.y + width / 2.0)
// Radius of the circle that encircles the polygon
// Notice that the radius is adjusted for the corners, that way the largest outer
// dimension of the resulting shape is always exactly the width - linewidth
let radius = (width - lineWidth + cornerRadius - (cos(theta) * cornerRadius)) / 2.0
// Start drawing at a point, which by default is at the right hand edge
// but can be offset
var angle = CGFloat(rotationOffset)
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle))
path.move(to: CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta)))
for _ in 0..<sides {
angle += theta
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle))
let tip = CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle))
let start = CGPoint(x: corner.x + cornerRadius * cos(angle - theta), y: corner.y + cornerRadius * sin(angle - theta))
let end = CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta))
path.addLine(to: start)
path.addQuadCurve(to: end, controlPoint: tip)
}
path.close()
backgroundMask.path = path.cgPath
}}
To add the gray hexagon under the blue animating path, you can add another CAShapeLayer:
var grayLayer = CAShapeLayer()
Set it up in a similar way to backgroundMask:
grayLayer.lineWidth = lineWidth
grayLayer.fillColor = nil
grayLayer.strokeColor = UIColor.gray.cgColor
Set its path to be the same path as backgroundMask:
backgroundMask.path = path.cgPath
grayLayer.path = path.cgPath
Finally, add the gray layer before you add backgroundMask. This makes it go at the bottom:
layer.addSublayer(grayLayer)
layer.addSublayer(backgroundMask)
Also note that your path drawing code doesn't need to go in draw. It could just go in init.
Here's what one frame of the animation looks like:

How to bend text like line segment into circle with core graphics?

The question was asked for BezierPath but now I not necessary to be with bezier it can be with core graphics also.
I am trying to draw curved text (shape it could be started as straight line to circle) and I saw CoreText allow us to draw text on UIBezierPath path. So I tried to draw a straight line and curved it into circle by given bend factor value. It should straight line if value is zero and if its positive bend line to down side else bend the line to up side [text will draw along this path.]
Did a way to bend straight line with given 'bend factor' into circle with UIBezierPath like this ?
i'have tried with way actually I am not convinced... Draw curve with 2 control points, if bend factor is 0 draw straight line else curve line while curve != half circle then updating path with half circle to complete path into a circle if bend factor is positive and doing reverse if bend factor is negative value it looks like straight line converts into circle but this not a good method to do this..For better understanding please take a look here
For example:
let r = textFXmodel.radius // This is bend factor
if r != 0.0 { // If bend factor is equals to zero draw straight line
let positiveR = abs(r)
let fullSize = self.bounds.size
var curvedLinePath = UIBezierPath()
let insetRect = self.bounds.insetBy(dx: 10, dy: 10)
let maximumValue = Float(insetRect.size.width / 2)
let minimumValue = -Float(insetRect.size.width / 2)
if r >= 0.0 { // If bend factor 'r' is positive value
if r <= CGFloat(maximumValue / 2) { // If bend factor 'r' less then half of circle ⌢
// Draw curved line and bend it from control points
let controlPoint1 = CGPoint(x: fullSize.width / 2 - (r + r * 0.2), y: fullSize.height / 2 - (r * 1.5))
let controlPoint2 = CGPoint(x: fullSize.width / 2 + (r + r * 0.2), y: fullSize.height / 2 - (r * 1.5))
let curveStartPoint = CGPoint(x: r, y: fullSize.height / 2)
let curveEndPoint = CGPoint(x: fullSize.width - r, y: fullSize.height / 2)
curvedLinePath.move(to: curveStartPoint)
curvedLinePath.addCurve(to: curveEndPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
} else { // bend factor 'r' is greater or equal to half circle so remove curved line and draw half circle path
let scaledRadius: CGFloat = r - 60
let centerPoint = CGPoint(x: fullSize.width / 2, y: fullSize.height / 2)
let startAngel = CGFloat.pi - (scaledRadius * 0.023)
let endAngle = (CGFloat.pi * 2) + (scaledRadius * 0.023)
curvedLinePath.removeAllPoints()
curvedLinePath = UIBezierPath(arcCenter: centerPoint, radius: (fullSize.width / 4), startAngle: startAngel, endAngle: endAngle, clockwise: true)
}
} else {
if r >= CGFloat(minimumValue / 2) {
let controlPoint1 = CGPoint(x: fullSize.width / 2 - (positiveR + positiveR * 0.2), y: fullSize.height / 2 + (positiveR * 1.5))
let controlPoint2 = CGPoint(x: fullSize.width / 2 + (positiveR + positiveR * 0.2), y: fullSize.height / 2 + (positiveR * 1.5))
let curveStartPoint = CGPoint(x: positiveR, y: fullSize.height / 2)
let curveEndPoint = CGPoint(x: fullSize.width - positiveR, y: fullSize.height / 2)
curvedLinePath.move(to: curveStartPoint)
curvedLinePath.addCurve(to: curveEndPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
} else {
let scaledRadius: CGFloat = positiveR - 60
let centerPoint = CGPoint(x: fullSize.width / 2, y: fullSize.height / 2)
let startAngel = CGFloat.pi + (scaledRadius * 0.023)
let endAngle = (CGFloat.pi * 2) - (scaledRadius * 0.023)
curvedLinePath.removeAllPoints()
curvedLinePath = UIBezierPath(arcCenter: centerPoint, radius: (fullSize.width / 4), startAngle: startAngel, endAngle: endAngle, clockwise: false)
}
}
// and here goes drawing code...
My code works like this it looks like line break at half of way (:
If you have a way to draw text like this without Bezierpath or CoreText please share it with me. I've googled a lot and I read all answers about this topic and no one helped me even this answer, and this
UPDATE:-
In this version line segment will bend in same center but this also not real circle it looks like drop shape
private func updatePath(withRadius radius: CGFloat) -> UIBezierPath {
var curveStartPoint: CGPoint = .zero
var curveEndPoint: CGPoint = .zero
var controlPoint1: CGPoint = .zero
var controlPoint2: CGPoint = .zero
if radius > 0.0 {
controlPoint1 = CGPoint(x: size.width / 2 - (radius + radius * 0.2), y: size.height / 2 - radius)
controlPoint2 = CGPoint(x: size.width / 2 + (radius + radius * 0.2), y: size.height / 2 - radius)
curveStartPoint = CGPoint(x: radius, y: size.height / 2 + (radius * 0.5))
curveEndPoint = CGPoint(x: size.width - radius, y: size.height / 2 + (radius * 0.5))
} else {
let positateR: CGFloat = abs(radius)
controlPoint1 = CGPoint(x: size.width / 2 - (positateR + positateR * 0.2), y: size.height / 2 + positateR)
controlPoint2 = CGPoint(x: size.width / 2 + (positateR + positateR * 0.2), y: size.height / 2 + positateR)
curveStartPoint = CGPoint(x: positateR, y: size.height / 2 + (radius * 0.5))
curveEndPoint = CGPoint(x: size.width - positateR, y: size.height / 2 + (radius * 0.5))
}
let drawingPath = UIBezierPath()
drawingPath.move(to: curveStartPoint)
drawingPath.addCurve(to: curveEndPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
return drawingPath
}

How to draw slices in semicircle shape layers in objective-c

I have drawn the semicircle shape by using following code
- (void)drawRect:(CGRect)rect
{
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath addArcWithCenter:CGPointMake(self.frame.size.width, self.frame.size.height/2) radius:150 startAngle:0 endAngle:2 * M_PI clockwise:YES];
CAShapeLayer *progressLayer = [[CAShapeLayer alloc] init];
[progressLayer setPath:bezierPath.CGPath];
[progressLayer setStrokeColor:[UIColor colorWithWhite:1. alpha:.2].CGColor];
[progressLayer setFillColor:[UIColor colorWithRed:67.0/255.0 green:144.0/255.0 blue:246.0/255.0 alpha:1.0].CGColor];
[progressLayer setLineWidth:.7 * self.bounds.size.width];
[[self layer] addSublayer:progressLayer];
// [self drawWheel];
}
I want to draw a slices like pie in a semicircle using objective-c that should match exactly like image as shown below
Can anyone suggest me any solution please
Add this code in your drawRect(_:)
let radius = min(bounds.size.width, bounds.size.height) / 2;
let center = CGPoint.init(x: bounds.size.width / 2, y: bounds.size.height / 2)
let sliceCount = 6;
let slicePath = UIBezierPath.init()
slicePath.lineWidth = 1;
slicePath.move(to: center)
var angle: CGFloat = 0 - (CGFloat.pi / 2);
var interval: CGFloat = (CGFloat.pi) / CGFloat(sliceCount)
for i in 0 ... sliceCount {
let x = center.x + (radius * cos(angle))
let y = center.y + (radius * sin(angle))
slicePath.addLine(to: CGPoint.init(x: x, y: y))
slicePath.move(to: center)
angle -= interval;
}
UIColor.white.setStroke()
slicePath.stroke()
Here is the result.
Updated
Another code that's very similar to your goal
override func draw(_ rect: CGRect) {
let radius = min(bounds.size.width, bounds.size.height) / 2;
let center = CGPoint.init(x: bounds.size.width, y: bounds.size.height / 2)
let sliceCount = 6;
let semiCirclePath = UIBezierPath.init()
semiCirclePath.move(to: center)
semiCirclePath.addArc(withCenter: center, radius: radius, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi + (CGFloat.pi / 2) , clockwise: true)
semiCirclePath.close()
UIColor.blue.setFill()
semiCirclePath.fill()
let slicePath = UIBezierPath.init()
slicePath.lineWidth = 1;
slicePath.move(to: center)
var angle: CGFloat = 0 - (CGFloat.pi / 2);
var interval: CGFloat = (CGFloat.pi) / CGFloat(sliceCount)
for i in 0 ... sliceCount {
let x = center.x + (radius * cos(angle))
let y = center.y + (radius * sin(angle))
slicePath.addLine(to: CGPoint.init(x: x, y: y))
slicePath.move(to: center)
angle -= interval;
}
UIColor.white.setStroke()
slicePath.stroke()
semiCirclePath.removeAllPoints()
semiCirclePath.move(to: center)
semiCirclePath.addArc(withCenter: center, radius: 30, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi + (CGFloat.pi / 2) , clockwise: true)
UIColor.lightGray.setFill()
semiCirclePath.fill()
}
Here what it looks like,
Updated second time
For the one that's completely looked like
override func draw(_ rect: CGRect) {
let radius = min(bounds.size.width, bounds.size.height) / 2;
let center = CGPoint.init(x: bounds.size.width, y: bounds.size.height / 2)
let selectedSliceIndex = 2;
let sliceCount = 6;
let slicePath = UIBezierPath.init()
let selectedSlicePath = UIBezierPath.init()
slicePath.lineWidth = 1;
slicePath.move(to: center)
var angle: CGFloat = 0 - (CGFloat.pi / 2);
let interval: CGFloat = (CGFloat.pi) / CGFloat(sliceCount)
for i in 0 ... sliceCount {
let x = center.x + (radius * cos(angle))
let y = center.y + (radius * sin(angle))
slicePath.addLine(to: CGPoint.init(x: x, y: y))
slicePath.move(to: center)
if i == selectedSliceIndex {
selectedSlicePath.move(to: center)
selectedSlicePath.addArc(withCenter: center, radius: radius, startAngle: angle - interval, endAngle: angle, clockwise: true)
selectedSlicePath.close()
}
angle -= interval;
}
// outer blue circle
let semiCirclePath = UIBezierPath.init()
semiCirclePath.move(to: center)
semiCirclePath.addArc(withCenter: center, radius: radius, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi + (CGFloat.pi / 2) , clockwise: true)
semiCirclePath.close()
UIColor.blue.setFill()
semiCirclePath.fill()
// lines
UIColor.white.setStroke()
slicePath.stroke()
// selected slice
UIColor.lightGray.setFill()
selectedSlicePath.fill()
UIColor.clear.setFill()
// inner gray circle
semiCirclePath.removeAllPoints()
semiCirclePath.move(to: center)
semiCirclePath.addArc(withCenter: center, radius: 30, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi + (CGFloat.pi / 2) , clockwise: true)
UIColor.lightGray.setFill()
semiCirclePath.fill()
}
You might need to adjust some values to look beautiful.
I Moved the view's frame inside ViewController FYI.

How to round corners of UIImage with Hexagon mask

I'm trying to add a hexagon mask to a UIImage which I has been successful in. However I am unable to round the sides of the hexagon mask. I thought adding cell.profilePic.layer.cornerRadius = 10; would do the trick but it hasn't.
Here is my code:
CGRect rect = cell.profilePic.frame;
CAShapeLayer *hexagonMask = [CAShapeLayer layer];
CAShapeLayer *hexagonBorder = [CAShapeLayer layer];
hexagonBorder.frame = cell.profilePic.layer.bounds;
UIBezierPath *hexagonPath = [UIBezierPath bezierPath];
CGFloat sideWidth = 2 * ( 0.5 * rect.size.width / 2 );
CGFloat lcolumn = ( rect.size.width - sideWidth ) / 2;
CGFloat rcolumn = rect.size.width - lcolumn;
CGFloat height = 0.866025 * rect.size.height;
CGFloat y = (rect.size.height - height) / 2;
CGFloat by = rect.size.height - y;
CGFloat midy = rect.size.height / 2;
CGFloat rightmost = rect.size.width;
[hexagonPath moveToPoint:CGPointMake(lcolumn, y)];
[hexagonPath addLineToPoint:CGPointMake(rcolumn, y)];
[hexagonPath addLineToPoint:CGPointMake(rightmost, midy)];
[hexagonPath addLineToPoint:CGPointMake(rcolumn, by)];
[hexagonPath addLineToPoint:CGPointMake(lcolumn, by)];
[hexagonPath addLineToPoint:CGPointMake(0, midy)];
[hexagonPath addLineToPoint:CGPointMake(lcolumn, y)];
hexagonMask.path = hexagonPath.CGPath;
hexagonBorder.path = hexagonPath.CGPath;
hexagonBorder.fillColor = [UIColor clearColor].CGColor;
hexagonBorder.strokeColor = [UIColor blackColor].CGColor;
hexagonBorder.lineWidth = 5;
cell.profilePic.layer.mask = hexagonMask;
cell.profilePic.layer.cornerRadius = 10;
cell.profilePic.layer.masksToBounds = YES;
[cell.profilePic.layer addSublayer:hexagonBorder];
Any ideas?
Thanks
You can define a path that is a hexagon with rounded corners (defining that path manually) and then apply that as the mask and border sublayer:
CGFloat lineWidth = 5.0;
UIBezierPath *path = [UIBezierPath polygonInRect:self.imageView.bounds
sides:6
lineWidth:lineWidth
cornerRadius:30];
// mask for the image view
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = path.CGPath;
mask.lineWidth = lineWidth;
mask.strokeColor = [UIColor clearColor].CGColor;
mask.fillColor = [UIColor whiteColor].CGColor;
self.imageView.layer.mask = mask;
// if you also want a border, add that as a separate layer
CAShapeLayer *border = [CAShapeLayer layer];
border.path = path.CGPath;
border.lineWidth = lineWidth;
border.strokeColor = [UIColor blackColor].CGColor;
border.fillColor = [UIColor clearColor].CGColor;
[self.imageView.layer addSublayer:border];
Where the path of a regular polygon with rounded corners might be implemented in a category like so:
#interface UIBezierPath (Polygon)
/** Create UIBezierPath for regular polygon with rounded corners
*
* #param rect The CGRect of the square in which the path should be created.
* #param sides How many sides to the polygon (e.g. 6=hexagon; 8=octagon, etc.).
* #param lineWidth The width of the stroke around the polygon. The polygon will be inset such that the stroke stays within the above square.
* #param cornerRadius The radius to be applied when rounding the corners.
*
* #return UIBezierPath of the resulting rounded polygon path.
*/
+ (instancetype)polygonInRect:(CGRect)rect sides:(NSInteger)sides lineWidth:(CGFloat)lineWidth cornerRadius:(CGFloat)cornerRadius;
#end
And
#implementation UIBezierPath (Polygon)
+ (instancetype)polygonInRect:(CGRect)rect sides:(NSInteger)sides lineWidth:(CGFloat)lineWidth cornerRadius:(CGFloat)cornerRadius {
UIBezierPath *path = [UIBezierPath bezierPath];
CGFloat theta = 2.0 * M_PI / sides; // how much to turn at every corner
CGFloat offset = cornerRadius * tanf(theta / 2.0); // offset from which to start rounding corners
CGFloat squareWidth = MIN(rect.size.width, rect.size.height); // width of the square
// calculate the length of the sides of the polygon
CGFloat length = squareWidth - lineWidth;
if (sides % 4 != 0) { // if not dealing with polygon which will be square with all sides ...
length = length * cosf(theta / 2.0) + offset/2.0; // ... offset it inside a circle inside the square
}
CGFloat sideLength = length * tanf(theta / 2.0);
// start drawing at `point` in lower right corner
CGPoint point = CGPointMake(rect.origin.x + rect.size.width / 2.0 + sideLength / 2.0 - offset, rect.origin.y + rect.size.height / 2.0 + length / 2.0);
CGFloat angle = M_PI;
[path moveToPoint:point];
// draw the sides and rounded corners of the polygon
for (NSInteger side = 0; side < sides; side++) {
point = CGPointMake(point.x + (sideLength - offset * 2.0) * cosf(angle), point.y + (sideLength - offset * 2.0) * sinf(angle));
[path addLineToPoint:point];
CGPoint center = CGPointMake(point.x + cornerRadius * cosf(angle + M_PI_2), point.y + cornerRadius * sinf(angle + M_PI_2));
[path addArcWithCenter:center radius:cornerRadius startAngle:angle - M_PI_2 endAngle:angle + theta - M_PI_2 clockwise:YES];
point = path.currentPoint; // we don't have to calculate where the arc ended ... UIBezierPath did that for us
angle += theta;
}
[path closePath];
path.lineWidth = lineWidth; // in case we're going to use CoreGraphics to stroke path, rather than CAShapeLayer
path.lineJoinStyle = kCGLineJoinRound;
return path;
}
That yields something like so:
Or in Swift 3 you might do:
let lineWidth: CGFloat = 5
let path = UIBezierPath(polygonIn: imageView.bounds, sides: 6, lineWidth: lineWidth, cornerRadius: 30)
let mask = CAShapeLayer()
mask.path = path.cgPath
mask.lineWidth = lineWidth
mask.strokeColor = UIColor.clear.cgColor
mask.fillColor = UIColor.white.cgColor
imageView.layer.mask = mask
let border = CAShapeLayer()
border.path = path.cgPath
border.lineWidth = lineWidth
border.strokeColor = UIColor.black.cgColor
border.fillColor = UIColor.clear.cgColor
imageView.layer.addSublayer(border)
With
extension UIBezierPath {
/// Create UIBezierPath for regular polygon with rounded corners
///
/// - parameter rect: The CGRect of the square in which the path should be created.
/// - parameter sides: How many sides to the polygon (e.g. 6=hexagon; 8=octagon, etc.).
/// - parameter lineWidth: The width of the stroke around the polygon. The polygon will be inset such that the stroke stays within the above square. Default value 1.
/// - parameter cornerRadius: The radius to be applied when rounding the corners. Default value 0.
convenience init(polygonIn rect: CGRect, sides: Int, lineWidth: CGFloat = 1, cornerRadius: CGFloat = 0) {
self.init()
let theta = 2 * .pi / CGFloat(sides) // how much to turn at every corner
let offset = cornerRadius * tan(theta / 2) // offset from which to start rounding corners
let squareWidth = min(rect.width, rect.height) // width of the square
// calculate the length of the sides of the polygon
var length = squareWidth - lineWidth
if sides % 4 != 0 { // if not dealing with polygon which will be square with all sides ...
length = length * cos(theta / 2) + offset / 2 // ... offset it inside a circle inside the square
}
let sideLength = length * tan(theta / 2)
// if you'd like to start rotated 90 degrees, use these lines instead of the following two:
//
// var point = CGPoint(x: rect.midX - length / 2, y: rect.midY + sideLength / 2 - offset)
// var angle = -CGFloat.pi / 2.0
// if you'd like to start rotated 180 degrees, use these lines instead of the following two:
//
// var point = CGPoint(x: rect.midX - sideLength / 2 + offset, y: rect.midY - length / 2)
// var angle = CGFloat(0)
var point = CGPoint(x: rect.midX + sideLength / 2 - offset, y: rect.midY + length / 2)
var angle = CGFloat.pi
move(to: point)
// draw the sides and rounded corners of the polygon
for _ in 0 ..< sides {
point = CGPoint(x: point.x + (sideLength - offset * 2) * cos(angle), y: point.y + (sideLength - offset * 2) * sin(angle))
addLine(to: point)
let center = CGPoint(x: point.x + cornerRadius * cos(angle + .pi / 2), y: point.y + cornerRadius * sin(angle + .pi / 2))
addArc(withCenter: center, radius: cornerRadius, startAngle: angle - .pi / 2, endAngle: angle + theta - .pi / 2, clockwise: true)
point = currentPoint
angle += theta
}
close()
self.lineWidth = lineWidth // in case we're going to use CoreGraphics to stroke path, rather than CAShapeLayer
lineJoinStyle = .round
}
}
For Swift 2 rendition, see previous revision of this answer.
Here is the swift 3 version of Rob's answer.
let lineWidth: CGFloat = 5.0
let path = UIBezierPath(roundedPolygonPathWithRect: self.bounds, lineWidth: lineWidth, sides: 6, cornerRadius: 12)
let mask = CAShapeLayer()
mask.path = path.cgPath
mask.lineWidth = lineWidth
mask.strokeColor = UIColor.clear.cgColor
mask.fillColor = UIColor.white.cgColor
self.layer.mask = mask
let border = CAShapeLayer()
border.path = path.cgPath
border.lineWidth = lineWidth
border.strokeColor = UIColor.black.cgColor
border.fillColor = UIColor.clear.cgColor
self.layer.addSublayer(border)
extension UIBezierPath {
convenience init(roundedPolygonPathWithRect rect: CGRect, lineWidth: CGFloat, sides: NSInteger, cornerRadius: CGFloat) {
self.init()
let theta = CGFloat(2.0 * M_PI) / CGFloat(sides)
let offSet = CGFloat(cornerRadius) / CGFloat(tan(theta/2.0))
let squareWidth = min(rect.size.width, rect.size.height)
var length = squareWidth - lineWidth
if sides%4 != 0 {
length = length * CGFloat(cos(theta / 2.0)) + offSet/2.0
}
let sideLength = length * CGFloat(tan(theta / 2.0))
var point = CGPoint(x: squareWidth / 2.0 + sideLength / 2.0 - offSet, y: squareWidth - (squareWidth - length) / 2.0)
var angle = CGFloat(M_PI)
move(to: point)
for _ in 0 ..< sides {
point = CGPoint(x: point.x + CGFloat(sideLength - offSet * 2.0) * CGFloat(cos(angle)), y: point.y + CGFloat(sideLength - offSet * 2.0) * CGFloat(sin(angle)))
addLine(to: point)
let center = CGPoint(x: point.x + cornerRadius * CGFloat(cos(angle + CGFloat(M_PI_2))), y: point.y + cornerRadius * CGFloat(sin(angle + CGFloat(M_PI_2))))
addArc(withCenter: center, radius:CGFloat(cornerRadius), startAngle:angle - CGFloat(M_PI_2), endAngle:angle + theta - CGFloat(M_PI_2), clockwise:true)
point = currentPoint // we don't have to calculate where the arc ended ... UIBezierPath did that for us
angle += theta
}
close()
}
}
Here's a conversion of the roundedPolygon method for Swift.
func roundedPolygonPathWithRect(square: CGRect, lineWidth: Float, sides: Int, cornerRadius: Float) -> UIBezierPath {
var path = UIBezierPath()
let theta = Float(2.0 * M_PI) / Float(sides)
let offset = cornerRadius * tanf(theta / 2.0)
let squareWidth = Float(min(square.size.width, square.size.height))
var length = squareWidth - lineWidth
if sides % 4 != 0 {
length = length * cosf(theta / 2.0) + offset / 2.0
}
var sideLength = length * tanf(theta / 2.0)
var point = CGPointMake(CGFloat((squareWidth / 2.0) + (sideLength / 2.0) - offset), CGFloat(squareWidth - (squareWidth - length) / 2.0))
var angle = Float(M_PI)
path.moveToPoint(point)
for var side = 0; side < sides; side++ {
let x = Float(point.x) + (sideLength - offset * 2.0) * cosf(angle)
let y = Float(point.y) + (sideLength - offset * 2.0) * sinf(angle)
point = CGPointMake(CGFloat(x), CGFloat(y))
path.addLineToPoint(point)
let centerX = Float(point.x) + cornerRadius * cosf(angle + Float(M_PI_2))
let centerY = Float(point.y) + cornerRadius * sinf(angle + Float(M_PI_2))
var center = CGPointMake(CGFloat(centerX), CGFloat(centerY))
let startAngle = CGFloat(angle) - CGFloat(M_PI_2)
let endAngle = CGFloat(angle) + CGFloat(theta) - CGFloat(M_PI_2)
path.addArcWithCenter(center, radius: CGFloat(cornerRadius), startAngle: startAngle, endAngle: endAngle, clockwise: true)
point = path.currentPoint
angle += theta
}
path.closePath()
return path
}
Maybe you have to round the corners of the border and the mask, rather than of the image view?
hexagonMask.cornerRadius = hexagonBorder.cornerRadius = 10.0;

Resources