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);
}
@toriaezunama
Copy link

Thank you. If I had to re-write this kind of code one time I might have lost it....

@AndrewBarba
Copy link

AndrewBarba commented Oct 26, 2016

Converted to Swift 3:

extension CGRect {

    func scaleToAspectFit(in rtarget: CGRect) -> CGFloat {
        // first try to match width
        let s = rtarget.width / self.width;
        // if we scale the height to make the widths equal, does it still fit?
        if self.height * s <= rtarget.height {
            return s
        }
        // no, match height instead
        return rtarget.height / self.height
    }

    func aspectFit(in rtarget: CGRect) -> CGRect {
        let s = scaleToAspectFit(in: rtarget)
        let w = width * s
        let h = height * s
        let x = rtarget.midX - w / 2
        let y = rtarget.midY - h / 2
        return CGRect(x: x, y: y, width: w, height: h)
    }

    func scaleToAspectFit(around rtarget: CGRect) -> CGFloat {
        // fit in the target inside the rectangle instead, and take the reciprocal
        return 1 / rtarget.scaleToAspectFit(in: self)
    }

    func aspectFit(around rtarget: CGRect) -> CGRect {
        let s = scaleToAspectFit(around: rtarget)
        let w = width * s
        let h = height * s
        let x = rtarget.midX - w / 2
        let y = rtarget.midY - h / 2
        return CGRect(x: x, y: y, width: w, height: 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