Arguably a better and more symmetric implementation of CGRect would be if it stored right/bottom instead of width/height.
struct MyRect: Equatable {
var left, top, right, bottom: CGFloat
// TODO: corner cases
func intersection(_ v: MyRect) -> MyRect {
let left = max(minX, v.minX)
let right = min(maxX, v.maxX)
let top = max(minY, v.minY)
let bottom = min(maxY, v.maxY)
return MyRect(left: left, top: top, right: right, bottom: bottom)
}
// TODO: corner cases
func contains(_ v: MyRect) -> Bool {
left <= v.left && right >= v.right && top <= v.top && bottom >= v.bottom
}
var width: CGFloat { right - left }
var height: CGFloat { bottom - top }
var minX: CGFloat { left }
var minY: CGFloat { top }
var maxX: CGFloat { right }
var maxY: CGFloat { bottom }
}
extension MyRect {
init(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) {
left = x
top = y
right = x + width
bottom = y + height
}
}
with "convenience" initialiser:
`init (x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat)`
in addition to the "main" one:
`init (left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat)`
Then intersect would be simpler and more reliable....
... and the following simple test would now fail:
let rect = MyRect(x: 100.33333333333333333333333, y: 0, width: 100, height: 100)
assert(rect.width == 100) // 99.99999999999999 != 100
I see these options to "handle all cases correctly":
-
"denial". ignore the issues of CGRect. Probably the best one, and definitely the easiest one.
-
"betterment". switch to storing right/bottom and then ignore the different set of new issues (see above). This is a breaking change, so only possible in, say, CoreGraphics v2 or whatever better is invented after CoreGraphics.
-
"cumbersome". invest in a more elaborate scheme where you store, say:
// Two of the following will be set, the remaining one should be nil:
var left: CGFloat?
var right: CGFloat?
var width: CGFloat?
...
and handle all cases (lot's of code). Likewise, this is a breaking change, so only possible in, say, CoreGraphics v2 or whatever better is invented after CoreGraphics.
-
"tolerance". compare coordinates with some tolerances. It's tricky to do correctly in general case and there'll be issues with transitivity a == b
, b == c
but a <> c
, which might or might not be a problem in a particular project. Strictly speaking this is also a breaking change, albeit less breaking than the other two.
-
"fractional". Switch from floating-point to fixed point coordinates (say 32 bits for integer part and 32 bit for fractional part). If I were creating "CoreGraphics v2" from scratch that would be my choice.
Edit. Added the fractional choice above.