DEV Community

Caleb Hearth
Caleb Hearth

Posted on • Originally published at calebhearth.com on

Custom Hexagon Shape in SwiftUI

XCode simulator of an iPhone 13 Mini in portrait orientation showing a black hexagon filling the width of the screen. The background is white.

Creating custom shapes in SwiftUI is pretty easy. At a high level, to satisfy the Shape protocol you just need to define path(in rect: CGRect) -> Path. Shape itself conforms to View, so you can even get a preview in XCode of your shape while you build it.

We’ll call this shape Hexagon, but really we’re building a regular hexagon, meaning that all sides and angles are coequal. You may be tempted to try to build a hexagon of six equilateral triangles, but it’s actually simpler with a bit of trigonometry. Each corner can be calculated by taking the center point + radius × cosine(π × index of the point) for the x coordinate and center point + radius × sine(π × index of the point) for the y coordinate.

Since we’re given a bounding rectangle, we find the center of that rectangle to be the center of the hexagon:

let center = CGPoint(x: rect.midX, y: rect.midY)

Enter fullscreen mode Exit fullscreen mode

The radius is the smaller of the height or width, divided by two:

let radius = min(rect.size.height, rect.size.width) / 2

Enter fullscreen mode Exit fullscreen mode

We can use those to calculate the corners, returning an array of CGPoint for each vertex of the hexagon:

func corners(center: CGPoint, radius: CGFloat) -> [CGPoint] {
    var points: [CGPoint] = []
    for i in (0...5) {
      let angle = CGFloat.pi / 3 * i
      let point = CGPoint(
        x: center.x + radius * cos(angle),
        y: center.y + radius * sin(angle)
      )
      points.append(point)
    }
    return points
}

Enter fullscreen mode Exit fullscreen mode

Finally, to actually build the Path that’s required from the path function, we’ll create a path (var path = Path()), move to the first vertex: path.move(to: corners[0], and then iterate over the points, calling path.addLine(to: point) for each one. Finally, we can call path.closeSubpath() to complete the outline and return the path.

Pulling everything together:

struct Hexagon: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius = min(rect.size.height, rect.size.width) / 2
        let corners = corners(center: center, radius: radius)
        path.move(to: corners[0])
        corners[1...5].forEach() { point in
            path.addLine(to: point)
        }
        path.closeSubpath()
        return path
    }

    func corners(center: CGPoint, radius: CGFloat) -> [CGPoint] {
        var points: [CGPoint] = []
        for i in (0...5) {
          let angle = CGFloat.pi / 3 * CGFloat(i)
          let point = CGPoint(
            x: center.x + radius * cos(angle),
            y: center.y + radius * sin(angle)
          )
          points.append(point)
        }
        return points
    }
}

Enter fullscreen mode Exit fullscreen mode

Shapes can be used in SwiftUI either directly, being added as a View right into another view, to clip an image (say, if you wanted to prove that you spent too much money and electricity on a URL to an image), and can be colored, resized, etc.

Top comments (0)