Given a custom Shape, how do you increase the size to fit a frame in SwiftUI?

Viewed 613

I have a series of custom defined Shape and a ZStack that has a set frame, is there a way to increase the size of each custom Shape that can be of various sizes to fit the Zstack frame?

enter image description here

Code that will draw various Shape given points.

struct DrawShape: Shape {

    var points: [CGPoint]
    
    func path(in rect: CGRect) -> Path {
        var path: Path = Path()

        guard let startingPoint = points.first else {
            return path
        }

        path.move(to: startingPoint)

        for pointLocation in points {
            
            path.addLine(to: pointLocation)
            
        }

        return path
    }
}
1 Answers

Here's what I came up with:

  1. Draw the path, as usual
  2. Get the boundingRect of the path
  3. Determine the scale factor needed for the path
    • rect is the available area of the shape
    • if boundingRect is fatter than rect, use their widths
    • if boundingRect is skinnier than rect, use their heights
  4. Draw a new path, this time with the scaled points and offset (if needed)
    • if the origin of the path isn't (0, 0), each new point needs to have the origin subtracted
  5. Return the new path
struct DrawShape: Shape {
    var points: [CGPoint]
    
    func path(in rect: CGRect) -> Path {
        
        /// draw the path
        var path = Path()
        guard let startingPoint = points.first else { return path }
        path.move(to: startingPoint)
        for pointLocation in points {
            path.addLine(to: pointLocation)
        }
        
        /// aspect fit scale
        let scale: CGFloat
        if path.boundingRect.height / path.boundingRect.width < rect.height / rect.width {
            scale = rect.width / path.boundingRect.width /// boundingRect is fatter
        } else {
            scale = rect.height / path.boundingRect.height /// boundingRect is skinnier
        }

        /// draw the scaled path
        var scaledPath = Path()
        scaledPath.move(to: convertPoint(startingPoint, offset: path.boundingRect.origin, scale: scale))
        for pointLocation in points {
            scaledPath.addLine(to: convertPoint(pointLocation, offset: path.boundingRect.origin, scale: scale))
        }
        
        /// return the scaled path
        return scaledPath
    }
    
    /// point = original point
    /// offset = in case the origin of `boundingRect` isn't (0,0), make sure to offset each point
    /// scale = how much to scale the point by
    func convertPoint(_ point: CGPoint, offset: CGPoint, scale: CGFloat) -> CGPoint {
        return CGPoint(x: (point.x - offset.x) * scale, y: (point.y - offset.y) * scale)
    }
}

struct ContentView: View {
    var body: some View {
        ZStack {
            Color.blue
            
            DrawShape(points: [
                CGPoint(x: 0, y: 0),
                CGPoint(x: 200, y: 0),
                CGPoint(x: 100, y: 250),
                CGPoint(x: 0, y: 0)
            ])
        }
        .frame(width: 300, height: 500)
    }
}
Before After
Small triangle at top-left of blue background Triangle scaled to fit width of background
Related