Skip to content

Instantly share code, notes, and snippets.

@lanephillips
Created October 7, 2013 21:05
Show Gist options
  • Save lanephillips/6874933 to your computer and use it in GitHub Desktop.
Save lanephillips/6874933 to your computer and use it in GitHub Desktop.
Objective-C code to fit a CGRect inside or outside another CGRect while maintaining aspect ratio. The fitted rectangle is centered on the target rectangle.
CGFloat ScaleToAspectFitRectInRect(CGRect rfit, CGRect rtarget)
{
// first try to match width
CGFloat s = CGRectGetWidth(rtarget) / CGRectGetWidth(rfit);
// if we scale the height to make the widths equal, does it still fit?
if (CGRectGetHeight(rfit) * s <= CGRectGetHeight(rtarget)) {
return s;
}
// no, match height instead
return CGRectGetHeight(rtarget) / CGRectGetHeight(rfit);
}
CGRect AspectFitRectInRect(CGRect rfit, CGRect rtarget)
{
CGFloat s = ScaleToAspectFitRectInRect(rfit, rtarget);
CGFloat w = CGRectGetWidth(rfit) * s;
CGFloat h = CGRectGetHeight(rfit) * s;
CGFloat x = CGRectGetMidX(rtarget) - w / 2;
CGFloat y = CGRectGetMidY(rtarget) - h / 2;
return CGRectMake(x, y, w, h);
}
CGFloat ScaleToAspectFitRectAroundRect(CGRect rfit, CGRect rtarget)
{
// fit in the target inside the rectangle instead, and take the reciprocal
return 1 / ScaleToAspectFitRectInRect(rtarget, rfit);
}
CGRect AspectFitRectAroundRect(CGRect rfit, CGRect rtarget)
{
CGFloat s = ScaleToAspectFitRectAroundRect(rfit, rtarget);
CGFloat w = CGRectGetWidth(rfit) * s;
CGFloat h = CGRectGetHeight(rfit) * s;
CGFloat x = CGRectGetMidX(rtarget) - w / 2;
CGFloat y = CGRectGetMidY(rtarget) - h / 2;
return CGRectMake(x, y, w, h);
}
@CharlesThierry
Copy link

CharlesThierry commented Apr 26, 2017

The AspectFitRectInRect(_, _) function behave closely to AVFoundation's AVMakeRectWithAspectRatioInsideRect() and has the same problem with images that has a close enough size.

For example, a (0, 0, 750, 1334) in a (0, 0, 1080, 1920) rectangle becomes a (0.26986506746618488, -1.1368683772161603e-13, 1079.4602698650676, 1920.0000000000002), which is close but not in a 1080 1920 rect -- especially when you try to get a CGRectIntegral() on it: (0, -1, 1080, 1921).

I believe the problem is with the approximation inherent to the CGFloat returned by the scaleToAspectFit(). Still, I'm pretty sure your solution will be enough for 99.9% of users, thanks for providing it.

I fix that little problem by not using the approx in the computation of the major dimension (the Height in the exemple) but by setting it directly to the major dimension of the target. Main con, the resulting rectangle ratio is not the same as the original.

CGRect AspectFitRectInRect(CGRect rfit, CGRect rtarget)
{
    CGFloat scale = 1;
    //default values to the target
    CGFloat w = CGRectGetWidth(rtarget);
    CGFloat h = CGRectGetHeight(rtarget);

    CGFloat s = CGRectGetWidth(rtarget) / CGRectGetWidth(rfit);
    // The greatest dim was preset, compute the lesser one
    if (CGRectGetHeight(rfit) * s <= CGRectGetHeight(rtarget))
    {
        scale = s;
        h = CGRectGetHeight(rfit) * scale;
    } else {
        scale = CGRectGetHeight(rtarget) / CGRectGetHeight(rfit);
        w = CGRectGetWidth(rfit) * scale;
    }

    // Center the resulting rect in the target
    CGFloat x = CGRectGetMidX(rtarget) - w / 2;
    CGFloat y = CGRectGetMidY(rtarget) - h / 2;
    return CGRectMake(x, y, w, h);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment