- 
      
- 
        Save tomasbasham/10533743 to your computer and use it in GitHub Desktop. 
| @implementation UIImage (scale) | |
| /** | |
| * Scales an image to fit within a bounds with a size governed by | |
| * the passed size. Also keeps the aspect ratio. | |
| * | |
| * Switch MIN to MAX for aspect fill instead of fit. | |
| * | |
| * @param newSize the size of the bounds the image must fit within. | |
| * @return a new scaled image. | |
| */ | |
| - (UIImage *)scaleImageToSize:(CGSize)newSize { | |
| CGRect scaledImageRect = CGRectZero; | |
| CGFloat aspectWidth = newSize.width / self.size.width; | |
| CGFloat aspectHeight = newSize.height / self.size.height; | |
| CGFloat aspectRatio = MIN ( aspectWidth, aspectHeight ); | |
| scaledImageRect.size.width = self.size.width * aspectRatio; | |
| scaledImageRect.size.height = self.size.height * aspectRatio; | |
| scaledImageRect.origin.x = (newSize.width - scaledImageRect.size.width) / 2.0f; | |
| scaledImageRect.origin.y = (newSize.height - scaledImageRect.size.height) / 2.0f; | |
| UIGraphicsBeginImageContextWithOptions( newSize, NO, 0 ); | |
| [self drawInRect:scaledImageRect]; | |
| UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext(); | |
| UIGraphicsEndImageContext(); | |
| return scaledImage; | |
| } | |
| @end | 
In Rubymotion
class UIImage
  # Scales an image to fit within a bounds with a size governed by
  # the passed size. Also keeps the aspect ratio.
  # 
  # newSize - the CGSize of the bounds the image must fit within.
  # aspect  - A Symbol stating the aspect mode (defaults: :min)
  #
  # Returns a new scaled UIImage
  def scaleImageToSize(newSize, aspect = :fit)
    scaledImageRect = CGRectZero
    aspectRules  = { :fill => :max } # else :min
    aspectWidth  = Rational(newSize.width,  size.width)
    aspectHeight = Rational(newSize.height, size.height)
    aspectRatio  = [aspectWidth, aspectHeight].send(aspectRules[aspect] || :min)
    scaledImageRect.size        = (size.width * aspectRatio).round
    scaledImageRect.size.height = (size.height * aspectRatio).round
    scaledImageRect.origin.x = Rational(newSize.width - scaledImageRect.size.width, 2.0).round
    scaledImageRect.origin.y = Rational(newSize.height - scaledImageRect.size.height, 2.0).round
    UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
    drawInRect(scaledImageRect)
    scaledImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    scaledImage
  end
endIn Swift 2 (with keeping aspect ratio)
func imageWithSize(size:CGSize) -> UIImage
{
    var scaledImageRect = CGRect.zero;
    let aspectWidth:CGFloat = size.width / self.size.width;
    let aspectHeight:CGFloat = size.height / self.size.height;
    let aspectRatio:CGFloat = min(aspectWidth, aspectHeight);
    scaledImageRect.size.width = self.size.width * aspectRatio;
    scaledImageRect.size.height = self.size.height * aspectRatio;
    scaledImageRect.origin.x = (size.width - scaledImageRect.size.width) / 2.0;
    scaledImageRect.origin.y = (size.height - scaledImageRect.size.height) / 2.0;
    UIGraphicsBeginImageContextWithOptions(size, false, 0);
    self.drawInRect(scaledImageRect);
    let scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}
If you want "aspect fill", instead of "aspect fit", change function min to max.
Good job!
great job reinderdevries
Thank you reinderdevries!!!
Thanks for the code everyone! Here is a Swift 3 version.
// MARK: - Image Scaling.
extension UIImage {
    /// Scales an image to fit within a bounds with a size governed by the passed size. Also keeps the aspect ratio.
    /// Switch MIN to MAX for aspect fill instead of fit.
    ///
    /// - parameter newSize: newSize the size of the bounds the image must fit within.
    ///
    /// - returns: a new scaled image.
    func scaleImageToSize(newSize: CGSize) -> UIImage {
        var scaledImageRect = CGRect.zero
        let aspectWidth = newSize.width/size.width
        let aspectheight = newSize.height/size.height
        let aspectRatio = max(aspectWidth, aspectheight)
        scaledImageRect.size.width = size.width * aspectRatio;
        scaledImageRect.size.height = size.height * aspectRatio;
        scaledImageRect.origin.x = (newSize.width - scaledImageRect.size.width) / 2.0;
        scaledImageRect.origin.y = (newSize.height - scaledImageRect.size.height) / 2.0;
        UIGraphicsBeginImageContext(newSize)
        draw(in: scaledImageRect)
        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return scaledImage!
    }
}
This method returns a blurry version of the original image for me even though it's scaled down.
Modification of @akshaynhegde for a more generic implementation using an enum for scaling mode, and a pretty interface:
The final interface is as simple as this:
image.scaled(to: size)
image.scaled(to: size, scalingMode: .aspectFill)
image.scaled(to: size, scalingMode: .aspectFit).. and is implemented like this:
// MARK: - Image Scaling.
extension UIImage {
    
    /// Represents a scaling mode
    enum ScalingMode {
        case aspectFill
        case aspectFit
        /// Calculates the aspect ratio between two sizes
        /// 
        /// - parameters:
        ///     - size:      the first size used to calculate the ratio
        ///     - otherSize: the second size used to calculate the ratio
        /// 
        /// - return: the aspect ratio between the two sizes
        func aspectRatio(between size: CGSize, and otherSize: CGSize) -> CGFloat {
            let aspectWidth  = size.width/otherSize.width
            let aspectHeight = size.height/otherSize.height
            switch self {
            case .aspectFill:
               return max(aspectWidth, aspectHeight)
            case .aspectFit:
                return min(aspectWidth, aspectHeight)
            }
        }
    }
    /// Scales an image to fit within a bounds with a size governed by the passed size. Also keeps the aspect ratio.
    ///
    /// - parameter:
    ///     - newSize:     the size of the bounds the image must fit within.
    ///     - scalingMode: the desired scaling mode
    ///
    /// - returns: a new scaled image.
    func scaled(to newSize: CGSize, scalingMode: UIImage.ScalingMode = .aspectFill) -> UIImage {
        
        let aspectRatio = scalingMode.aspectRatio(between: newSize, and: size)
        
        /* Build the rectangle representing the area to be drawn */
        var scaledImageRect = CGRect.zero
        scaledImageRect.size.width  = size.width * aspectRatio
        scaledImageRect.size.height = size.height * aspectRatio
        scaledImageRect.origin.x    = (newSize.width - size.width * aspectRatio) / 2.0
        scaledImageRect.origin.y    = (newSize.height - size.height * aspectRatio) / 2.0
        /* Draw and retrieve the scaled image */
        UIGraphicsBeginImageContext(newSize)
        draw(in: scaledImageRect)
        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return scaledImage!
    }
}This could be improved to support more scaling modes such as .fit, but the topic says keeping the aspect ratio.
In Xamarin.iOS
public static class UIImageHelper
{
    public static UIImage ScaledImage(this UIImage self, CGSize newSize)
    {
        CGRect scaledImageRect = CGRect.Empty;
        double aspectWidth = newSize.Width / self.Size.Width;
        double aspectHeight = newSize.Height / self.Size.Height;
        double aspectRatio = Math.Min(aspectWidth, aspectHeight);
        scaledImageRect.Size = new CGSize(self.Size.Width * aspectRatio, self.Size.Height * aspectRatio);
        scaledImageRect.X = (newSize.Width - scaledImageRect.Size.Width) / 2.0f;
        scaledImageRect.Y = (newSize.Height - scaledImageRect.Size.Height) / 2.0f;
        UIGraphics.BeginImageContextWithOptions(newSize, false, 0);
        self.Draw(scaledImageRect);
        UIImage scaledImage = UIGraphics.GetImageFromCurrentImageContext();
        UIGraphics.EndImageContext();
        return scaledImage;
    }
}
@Oyvindkg, if you call this method with .aspectFit it produces an image with the specified 'newSize' size (let's say 800x800). What you would expect to get is an image scaled according to its aspect ratio with max 800 width or height (depending on the image size and form factor). In this example, the result is a squared, resized image of (e.g.) a landscape picture.
This can be fixed by setting the origin coordinates to 0:
scaledImageRect.origin.x = 0
scaledImageRect.origin.y = 0
and by using the computed, scaled rect as input for the UIGraphicsContext to begin:
UIGraphicsBeginImageContext(scaledImageRect.size)
And if the image is in a URL type http://domain.com/myimage.jpg obtained with JSON, as I do to resize it to show it in a UIImage so that it keeps the ratio?
Thank you for your work, guys!
@akshaynhegde, @Oyvindkg, the method returns a blurry version of the original image, it is scaled, but blurry. Any idea how to fix it?
Swift 4
extension UIImage {
  func scale(with size: CGSize) -> UIImage? {
    var scaledImageRect = CGRect.zero
    
    let aspectWidth:CGFloat = size.width / self.size.width
    let aspectHeight:CGFloat = size.height / self.size.height
    let aspectRatio:CGFloat = min(aspectWidth, aspectHeight)
    
    scaledImageRect.size.width = self.size.width * aspectRatio
    scaledImageRect.size.height = self.size.height * aspectRatio
    scaledImageRect.origin.x = (size.width - scaledImageRect.size.width) / 2.0
    scaledImageRect.origin.y = (size.height - scaledImageRect.size.height) / 2.0
    
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    
    self.draw(in: scaledImageRect)
    
    let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    return scaledImage
  }
}@shinrikiken - your code is not giving me the perfect image which should fits to the image size.
@lee-ho, perhaps you can replace this line
UIGraphicsBeginImageContext(newSize)
with this
UIGraphicsBeginImageContextWithOptions(newSize, false, UIScreen.main.scale)
and try?
Does anyone have the version of this to work in NSImage (for MacOS)?
Swift 5 (Mac Os)
This working for me but I'm sure if is the best approach
extension NSImage {
    
    /// Represents a scaling mode
    enum ScalingMode {
        case aspectFill
        case aspectFit
        
        /// Calculates the aspect ratio between two sizes
        ///
        /// - parameters:
        ///     - size:      the first size used to calculate the ratio
        ///     - otherSize: the second size used to calculate the ratio
        ///
        /// - return: the aspect ratio between the two sizes
        func aspectRatio(between size: CGSize, and otherSize: CGSize) -> CGFloat {
            let aspectWidth  = size.width/otherSize.width
            let aspectHeight = size.height/otherSize.height
            
            switch self {
            case .aspectFill:
                return max(aspectWidth, aspectHeight)
            case .aspectFit:
                return min(aspectWidth, aspectHeight)
            }
        }
    }
    
    /// Scales an image to fit within a bounds with a size governed by the passed size. Also keeps the aspect ratio.
    ///
    /// - parameter:
    ///     - newSize:     the size of the bounds the image must fit within.
    ///     - scalingMode: the desired scaling mode
    ///
    /// - returns: a new scaled image.
    func scaled(to newSize: CGSize, scalingMode: ScalingMode = .aspectFill) -> NSImage {
        
        let aspectRatio = scalingMode.aspectRatio(between: newSize, and: size)
        
        /* Build the rectangle representing the area to be drawn */
        var scaledImageRect = CGRect.zero
        
        scaledImageRect.size.width  = size.width * aspectRatio
        scaledImageRect.size.height = size.height * aspectRatio
        scaledImageRect.origin.x    = (newSize.width - size.width * aspectRatio) / 2.0
        scaledImageRect.origin.y    = (newSize.height - size.height * aspectRatio) / 2.0
        
        let scaledImage = NSImage(size: newSize)
        scaledImage.lockFocus()
        draw(in: scaledImageRect)
        scaledImage.unlockFocus()
        
        return scaledImage
    }
}
In Swift: