Reputation: 1431
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?
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
}
}
Upvotes: 3
Views: 1589
Reputation: 5125
There is a way to fit any path inside the proposed rectangle keeping aspect ratio. And it's rather short way.
struct FittingShape: Shape {
var absolutePath: Path
func path(in rect: CGRect) -> Path {
let boundingRect = absolutePath.boundingRect
let scale = min(rect.width/boundingRect.width, rect.height/boundingRect.height)
let scaled = absolutePath.applying(.init(scaleX: scale, y: scale))
let scaledBoundingRect = scaled.boundingRect
let offsetX = scaledBoundingRect.midX - rect.midX
let offsetY = scaledBoundingRect.midY - rect.midY
return scaled.offsetBy(dx: -offsetX, dy: -offsetY)
}
}
Test it:
struct ContentView: View {
var path = Path {
$0.addLines([
CGPoint(x: 0, y: 0),
CGPoint(x: 200, y: 0),
CGPoint(x: 100, y: 250),
CGPoint(x: 0, y: 0)
])
}
var body: some View {
ZStack {
Color.blue
FittingShape(absolutePath: path) // The shape is centered
//DrawShape(points: points) // This gives sligtly different result: the shape is sticked to the top
}
.frame(width: 300, height: 500)
}
}
Upvotes: 1
Reputation: 30554
Here's what I came up with:
boundingRect
of the pathrect
is the available area of the shapeboundingRect
is fatter than rect
, use their widthsboundingRect
is skinnier than rect
, use their heights(0, 0)
, each new point needs to have the origin subtractedstruct 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)
}
}
Upvotes: 2