You can't do that with just a single call to min()
/max()
off of the highest and lowest of the y and x coordinates
The point with the minimal x value could be different from the point with the minimal x value. So any method that returns only one of the points (as min()
/max()
do) isn't sufficient.
You either need two separate calls to each, like:
let minX = points.min(by: { $0.x < $1.x })!.x
let minY = points.min(by: { $0.y < $1.y })!.y
let maxX = points.min(by: { $0.x > $1.x })!.x
let maxY = points.min(by: { $0.y > $1.y })!.y
or you could try doing it all in one swoop with reduce:
let initialAccumulator = (
minX: CGFloat.max, minY: CGFloat.max, maxX: CGFloat.min, maxY: CGFloat.min)
let (minX, minY, maxX, maxY) = points.reduce(initialAccumulator) { accumulator, point in
return (
minX: min(accumulator.minX, point.x),
minY: min(accumulator.minY, point.y),
maxX: max(accumulator.maxX, point.x),
maxY: max(accumulator.maxY, point.y)
)
}
I would probably do this like so:
extension CGRect {
static func minimalRect(containing points: [CGPoint]) -> CGRect? {
if points.isEmpty { return nil }
let minX = points.min(by: { $0.x < $1.x })!.x
let minY = points.min(by: { $0.y < $1.y })!.y
let maxX = points.min(by: { $0.x > $1.x })!.x
let maxY = points.min(by: { $0.y > $1.y })!.y
return CGRect(
x: minX,
y: minY,
width: (minX - maxX).magnitude,
height: (minY - maxY).magnitude
)
}
}
let points = [(1234.0, 1053.0), (1241.0, 1111.0), (1152.0, 1043.0)].map(CGPoint.init)
let r = CGRect.minimalRect(containing: points)
print(r ?? .zero) // => (1152.0, 1043.0, 89.0, 68.0)