swiftui

How to solve the size and shape deformation issue using a defined Path in SwiftUI?


I have a Hexagon path with this code and photo:

 struct HorizontalHexagon: InsettableShape {
    
    var insetAmount: CGFloat = CGFloat.zero

    func path(in rect: CGRect) -> Path {

        return Path { path in
            path.move(to: CGPoint(x: rect.minX + insetAmount, y: rect.midY))
            path.addLine(to: CGPoint(x: rect.maxX/4.0, y: rect.minY + insetAmount))
            path.addLine(to: CGPoint(x: rect.maxX - rect.maxX/4.0, y: rect.minY + insetAmount))
            
            path.addLine(to: CGPoint(x: rect.maxX - insetAmount, y: rect.midY))
            
            path.addLine(to: CGPoint(x: rect.maxX - rect.maxX/4.0, y: rect.maxY - insetAmount))
            path.addLine(to: CGPoint(x: rect.maxX/4.0, y: rect.maxY - insetAmount))
   
            path.closeSubpath()
            
        }
    }
    
    func inset(by amount: CGFloat) -> some InsettableShape {
        var myShape: Self = self
        myShape.insetAmount += amount
        return myShape
    }
    
}

enter image description here

However, when I use this defined path to build another shape, there is a size and shape deformation. I don't see any mistakes in my code, but for some unknown reason, this issue occurs.

struct FinalShape: InsettableShape {
    
    var insetAmount: CGFloat = CGFloat.zero

    let size1: Double = 4.0
    
    func path(in rect: CGRect) -> Path {
        return Path { path in
            
            let makeSize1: Double = rect.width/size1
            let makeSize2: Double = rect.width - makeSize1
            let horizontalHexagon = HorizontalHexagon(insetAmount: insetAmount)

            path.addPath(horizontalHexagon.path(in: CGRect(origin: CGPoint(x: rect.minX + makeSize1/2, y: rect.minY), size: CGSize(width: makeSize2, height: makeSize1))))
            path.addPath(horizontalHexagon.path(in: CGRect(origin: CGPoint(x: rect.minX + makeSize1/2, y: rect.midY - makeSize1/2), size: CGSize(width: makeSize2, height: makeSize1))))
            path.addPath(horizontalHexagon.path(in: CGRect(origin: CGPoint(x: rect.minX + makeSize1/2, y: rect.maxY - makeSize1), size: CGSize(width: makeSize2, height: makeSize1))))
 
        }
    }
    
    func inset(by amount: CGFloat) -> some InsettableShape {
        var myShape: Self = self
        myShape.insetAmount += amount
        return myShape
    }
}

enter image description here

My code for ContentView:

struct ContentView: View {
    var body: some View {
        
        FinalShape()
            .strokeBorder(style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
            .background(Color.purple)
            .cornerRadius(5.0)
            .padding()

    }
}

UPDATED:

struct HorizontalHexagon: InsettableShape {
    
    var insetAmount: CGFloat = CGFloat.zero

    func path(in rect: CGRect) -> Path {

        return Path { path in
            path.move(to: CGPoint(x: rect.minX + insetAmount, y: rect.midY))
            path.addLine(to: CGPoint(x: rect.width/4, y: rect.minY + insetAmount))
            path.addLine(to: CGPoint(x: rect.maxX - rect.width/4, y: rect.minY + insetAmount))
            
            path.addLine(to: CGPoint(x: rect.maxX - insetAmount, y: rect.midY))
            
            path.addLine(to: CGPoint(x: rect.maxX - rect.width/4, y: rect.maxY - insetAmount))
            path.addLine(to: CGPoint(x: rect.width/4, y: rect.maxY - insetAmount))
   
            path.closeSubpath()
            
        }
    }
    
    func inset(by amount: CGFloat) -> some InsettableShape {
        var myShape: Self = self
        myShape.insetAmount += amount
        return myShape
    }
    
}

Solution

  • When implementing a Shape, do not assume rect.origin == .zero.

    SwiftUI calls rect(in:) with a zero-origin rectangle almost all the time, but in this case you are calling rect(in:) yourself, with rectangles that have a non-zero origin.

    So

    maxX/minX are points in the rectangle, so it almost never makes sense to divide them.

    func path(in rect: CGRect) -> Path {
    
       return Path { path in
           path.move(to: CGPoint(x: rect.minX + insetAmount, y: rect.midY))
           path.addLine(to: CGPoint(x: rect.minX + rect.width / 4.0, y: rect.minY + insetAmount))
           path.addLine(to: CGPoint(x: rect.maxX - rect.width / 4.0, y: rect.minY + insetAmount))
           
           path.addLine(to: CGPoint(x: rect.maxX - insetAmount, y: rect.midY))
           
           path.addLine(to: CGPoint(x: rect.maxX - rect.width / 4.0, y: rect.maxY - insetAmount))
           path.addLine(to: CGPoint(x: rect.minX + rect.width / 4.0, y: rect.maxY - insetAmount))
    
           path.closeSubpath()
       }
    }
    

    That said, I find the way this shape is insetted quite weird, the vertical parts insets "more" than the two sides. See this post for some ideas.