Insetting, offsetting, transformation, and more!
The humble CGRect
struct might seem like a primitive beast, but it has a number of useful methods that let you adjust rectangles i a variety of ways.
Here are two simple CGRect
instances we can work with:
let rect1 = CGRect(x: 20, y: 20, width: 100, height: 100)
let rect2 = CGRect(x: 50, y: 50, width: 100, height: 100)
You can see that they overlap each other, and CGRect
has three methods that let us evaluate that overlap. The first is the simplest: intersects()
returns true if two rectangles overlap. In our case, this would be true:
rect1.intersects(rect2)
If you want more details, the intersection()
method tells you where the intersection lies. Both our rectangles are 100 points wide, but the second one starts at X:50 and Y:50 so that’s where our overlap will begin. So, this code will return a new CGRect
containing X:50 Y:50 width:70 height:70:
rect1.intersection(rect2)
The third option is the contains()
method, which will return true if one rectangle entirely contains another. So, this will return false for us because the second rectangle lies partly outside the first:
rect1.contains(rect2)
There are four useful methods for transforming rectangles, but by far the most useful are insetBy()
and offsetBy()
. The former is used for expanding or contracting rectangles, and the latter for adjusting their positions.
For example, this will produce a rectangle at X:40 Y:40 width:60 height:60:
rect1.insetBy(dx: 20, dy: 20)
You’re specifying how much to grow or shrink the rectangle horizontally (dx
) and vertically (dy
) – these numbers apply to all four edges, which is why a horizontal change of 20 took our width from 100 to 60.
You can also make rectangles larger, like this:
rect1.insetBy(dx: -20, dy: -20)
I find negative insets helpful if you’re drawing behind something and want to make sure you have a fixed amount of margin on all edges.
As for offsetBy()
, this is simply designed to move rectangles by whatever X and Y amount you need. Internally it just modifies the origin of the rectangle while also making sure it has positive width and height values, but using offsetBy()
helps make your intent clearer.
So, this will move our rectangle 10 points down and to the right:
rect1.offsetBy(dx: 10, dy: 10)
For more advanced transformations, you can apply any CGAffineTransform
to a CGRect
by calling its applying()
method. You should experiment in a playground first, though – these transforms work in their own unique way.
As an example, take a look at this code:
let transform = CGAffineTransform(scaleX: 2, y: 2)
rect1.applying(transform)
Our rect1
constant had X:20 Y:20 width:100 height:100, so after scaling by 2x there are several possible outcomes. For example, you might think that X and Y stay the same, but width and height double, or you might think that width and height double, but X and Y go down by 50 so the rectangle stays centered.
However, what this method actually does is scale up all four coordinate components: X, Y, width, and height all double, meaning that the finished rectangle starts at X:40 Y:40 – it has been scaled relative to the top-left corner.
One last method that’s worth mentioning is divided(atDistance:)
, which lets you easily split a rectangle into two parts. This returns two rectangles: the slice up to the point you specified, and the remainder afterwards.
As an example, we could divide rect1
into two rectangles, with the first slice taking up the first 40% of rect1
and the remainder taking up everything else:
let (slice, remainder) = rect1.divided(atDistance: rect1.width * 0.4, from: .minXEdge)
All these methods are things you could yourself, usually in a dozen lines of code or less. But do you really want to be spending your time thinking about rectangle transformations when Apple can do all that work for you? Probably not. CGRect
isn't a complex struct, but it's worth letting it work its magic where possible!
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure and A/B test your entire paywall UI without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.