Skip to content

Instantly share code, notes, and snippets.

@henrinormak
Last active September 18, 2021 14:59
Show Gist options
  • Save henrinormak/96c8918e7baa23d5b10a to your computer and use it in GitHub Desktop.
Save henrinormak/96c8918e7baa23d5b10a to your computer and use it in GitHub Desktop.
import UIKit
public func roundedPolygonPath(rect: CGRect, lineWidth: CGFloat, sides: NSInteger, cornerRadius: CGFloat, rotationOffset: CGFloat = 0) -> UIBezierPath {
let path = UIBezierPath()
let theta: CGFloat = CGFloat(2.0 * Double.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()
// 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
}
public func createImage(layer: CALayer) -> UIImage! {
let size = CGSize(width: layer.frame.maxX, height: layer.frame.maxY)
UIGraphicsBeginImageContextWithOptions(size, layer.isOpaque, 0.0)
let ctx = UIGraphicsGetCurrentContext()!
layer.render(in: ctx)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
let lineWidth = CGFloat(2.0)
let rect = CGRect(x: 0.0, y: 0.0, width: 150.0, height: 150.0)
let sides = 5
let rotationOffset = CGFloat(Double.pi / 2.0)
var path = roundedPolygonPath(rect: rect, lineWidth: lineWidth, sides: sides, cornerRadius: 15.0, rotationOffset: rotationOffset)
let borderLayer = CAShapeLayer()
borderLayer.frame = CGRect(x: 0.0, y: 0.0, width: path.bounds.width + lineWidth, height: path.bounds.height + lineWidth)
borderLayer.path = path.cgPath
borderLayer.lineWidth = lineWidth
borderLayer.lineJoin = kCALineJoinRound
borderLayer.lineCap = kCALineCapRound
borderLayer.strokeColor = UIColor.black.cgColor
borderLayer.fillColor = UIColor.white.cgColor
var image = createImage(layer: borderLayer)
@henrinormak
Copy link
Author

I updated my gist literally just now (without noticing the new comment), so might be some overlap.

@Darmaal
Copy link

Darmaal commented Mar 14, 2018

Swift 4


public 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
}

public func createImage(layer: CALayer) -> UIImage {
    let size = CGSize(width:  layer.frame.size.width, height: layer.frame.size.height)
    UIGraphicsBeginImageContextWithOptions(size, layer.isOpaque, 0.0)
    let ctx = UIGraphicsGetCurrentContext()
    
    layer.render(in: ctx!)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    
    UIGraphicsEndImageContext()
    
    return image!
}
    
    let lineWidth = CGFloat(7.0)
    let rect = CGRect(x: 0.0, y: 0.0, width: 150, height: 150)
    let sides = 6
    
    var path = roundedPolygonPath(rect: rect, lineWidth: lineWidth, sides: sides, cornerRadius: 15.0, rotationOffset: CGFloat(-M_PI / 2.0))
    
    let borderLayer = CAShapeLayer()
    borderLayer.frame = CGRect(x : 0.0, y : 0.0, width : path.bounds.width + lineWidth, height : path.bounds.height + lineWidth)
    borderLayer.path = path.cgPath
    borderLayer.lineWidth = lineWidth
    borderLayer.lineJoin = kCALineJoinRound
    borderLayer.lineCap = kCALineCapRound
    borderLayer.strokeColor = UIColor.black.cgColor
    borderLayer.fillColor = UIColor.white.cgColor
    
    let hexagon = createImage(layer: borderLayer)



@alexandru-gaidei
Copy link

Xamarin iOS

public static UIBezierPath RoundedPolygonPath(CGRect rect, float lineWidth, int sides, float cornerRadius, float angle)
        {
            var path = new UIBezierPath();

            var theta = (float)(2 * Math.PI / sides);
            var offset = cornerRadius * Math.Tan(theta / 2);
            var width = Math.Min(rect.Size.Width, rect.Size.Height);
            var center = new CGPoint(x: rect.Location.X + width / 2, y: rect.Location.Y + width / 2);
            var radius = (width - lineWidth + cornerRadius - (Math.Cos(theta) * cornerRadius)) / 2;
            var corner = new CGPoint(center.X + (radius - cornerRadius) * Math.Cos(angle), center.Y + (radius - cornerRadius) * Math.Sin(angle));

            path.MoveTo(new CGPoint(corner.X + cornerRadius * Math.Cos(angle + theta), corner.Y + cornerRadius * Math.Sin(angle + theta)));

            for (int i = 0; i < sides; i++)
            {
                angle += theta;
                var _corner = new CGPoint(center.X + (radius - cornerRadius) * Math.Cos(angle), center.Y + (radius - cornerRadius) * Math.Sin(angle));
                var tip = new CGPoint(center.X + radius * Math.Cos(angle), center.Y + radius * Math.Sin(angle));
                var start = new CGPoint(_corner.X + cornerRadius * Math.Cos(angle - theta), _corner.Y + cornerRadius * Math.Sin(angle - theta));
                var end = new CGPoint(_corner.X + cornerRadius * Math.Cos(angle + theta), _corner.Y + cornerRadius * Math.Sin(angle + theta));

                path.AddLineTo(start);
                path.AddQuadCurveToPoint(end, controlPoint: tip);
            }

            path.ClosePath();
            var bounds = path.Bounds;
            var transform = CGAffineTransform.MakeTranslation(-bounds.Location.X + rect.Location.X + lineWidth / 2, -bounds.Location.Y + rect.Location.Y + lineWidth / 2);
            path.ApplyTransform(transform);

            return path;
        }

@emrcftci
Copy link

Swift 5

private func roundedPolygonPath(
    rect: CGRect,
    lineWidth: CGFloat,
    sides: NSInteger,
    cornerRadius: CGFloat,
    rotationOffset: CGFloat = 0
) -> UIBezierPath {

    let path = UIBezierPath()
    let theta: CGFloat = CGFloat(2.0 * Double.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)))

    (0..<sides).forEach { _ in
      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
}

public func image(from layer: CALayer) -> UIImage {
    let size = CGSize(width: layer.frame.maxX, height: layer.frame.maxY)
    UIGraphicsBeginImageContextWithOptions(size, layer.isOpaque, 0.0)
    let context = UIGraphicsGetCurrentContext()!

    layer.render(in: context)
    let image = UIGraphicsGetImageFromCurrentImageContext()

    UIGraphicsEndImageContext()

    return image ?? UIImage()
}

Usage

let lineWidth = CGFloat(2.0)
let rect = CGRect(x: 0.0, y: 0.0, width: 150.0, height: 150.0)
let sides = 5
let rotationOffset = CGFloat(Double.pi / 2.0)

let path = roundedPolygonPath(rect: rect,
                              lineWidth: lineWidth,
                              sides: sides,
                              cornerRadius: 15.0,
                              rotationOffset: rotationOffset)

let borderLayer = CAShapeLayer()
borderLayer.frame = CGRect(x: 0.0, y: 0.0, width: path.bounds.width + lineWidth, height: path.bounds.height + lineWidth)
borderLayer.path = path.cgPath
borderLayer.lineWidth = lineWidth
borderLayer.lineJoin = .round
borderLayer.lineCap = .round
borderLayer.strokeColor = UIColor.black.cgColor
borderLayer.fillColor = UIColor.white.cgColor

var image = image(from: borderLayer)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment