swiftswiftui

How to implement insetAmount as dx and dy in a HexagonShape for strokeBorder in SwiftUI?


I have a shape like in code below:

import SwiftUI

struct ContentView: View {
    var body: some View {
        
        let myCustomInsettableShape = HexagonShape(thickness: 100.0)
        
        ZStack {

            myCustomInsettableShape
                .fill(Color.blue)

            myCustomInsettableShape
             .strokeBorder(Color.black.opacity(0.5), style: StrokeStyle(lineWidth: 50.0, lineCap: .round, lineJoin: .miter))
            
        }
        .background(Color.red)
        .padding(50)
    }
}


struct HexagonShape: InsettableShape {
    
    var thickness: CGFloat
    var insetAmount: CGFloat = CGFloat.zero
    
    func path(in rect: CGRect) -> Path {
        
        var path: Path = Path()
        
        let halfThickness: CGFloat = thickness/2.0

        let dx: CGFloat
        let dy: CGFloat
        
        if (insetAmount > .zero) && (halfThickness > .zero) {
            let diagonal = sqrt((halfThickness * halfThickness) + (halfThickness * halfThickness))
            dx = insetAmount * diagonal / halfThickness
            dy = insetAmount * diagonal / halfThickness
        } else {
            dx = .zero
            dy = .zero
        }

        path.move(to: CGPoint(x: rect.minX + dx, y: rect.minY + halfThickness))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.minY + dy))
        path.addLine(to: CGPoint(x: rect.maxX - dx, y: rect.minY + halfThickness))
        path.addLine(to: CGPoint(x: rect.maxX - dx, y: rect.maxY - halfThickness))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY - dy))
        path.addLine(to: CGPoint(x: rect.minX + dx, y: rect.maxY - halfThickness))
        path.closeSubpath()

        return path
        
    }
    
    func inset(by amount: CGFloat) -> some InsettableShape {
        var myShape: Self = self
        myShape.insetAmount += amount
        return myShape
    }
    
}

The result of shape is like this:

enter image description here

The blue filled shape is what I want, and i want my strokeBorder have the same shape, but I do not know how to implement insetAmount, thickness is a value that can be changed showing and telling the form of shape.


Solution

  • I think the geometry is like this:

    Screenshot

    When this is used as the basis for implementation, it seems to work:

    func path(in rect: CGRect) -> Path {
        var path: Path = Path()
        let halfThickness: CGFloat = thickness/2.0
        let dyTop: CGFloat
        let dySide: CGFloat
    
        if insetAmount > 0 && rect.width > 0 && rect.height > 0 {
            let alpha = atan2(thickness, rect.width)
            dyTop = insetAmount / cos(alpha)
            let sideAngle = alpha + (Double.pi / 2)
            let beta = sideAngle / 2
            dySide = insetAmount / tan(beta)
        } else {
            dyTop = 0
            dySide = 0
        }
        path.move(to: CGPoint(x: rect.minX + insetAmount, y: rect.minY + halfThickness + dySide))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.minY + dyTop))
        path.addLine(to: CGPoint(x: rect.maxX - insetAmount, y: rect.minY + halfThickness + dySide))
        path.addLine(to: CGPoint(x: rect.maxX - insetAmount, y: rect.maxY - halfThickness - dySide))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY - dyTop))
        path.addLine(to: CGPoint(x: rect.minX + insetAmount, y: rect.maxY - halfThickness - dySide))
        path.closeSubpath()
        return path
    }
    

    Screenshot