Search code examples
iosswiftuiscrollviewuiimageimage-scaling

How to scale a UIImage to fit screen based on height and width of device


I have a UIScrollView and inside it i have a UIImage which i get from my server. Each image will either have its width greater than the height or the height greater than its width.

I would like to scale the image in such a way that it fits my device screen perfect based on the height or width

For example: I get the image from server and check which is greater, the width or the height.

if let width = image?.size.width, let height = image?.size.height {
    if width > height {
        print("Width is greater than height")
    }else              
        print("Height is greater than width")
    }                
}

Once i do this i would like to scale the image in one of these 2 ways.

Option 1: If the width is greater than the height then i would like the image to be scaled with respect to its height being fixed to device height and width should be scrollable in the view.

Option 2: If the height is greater than the width then i would like the image to be scaled with respect to its width being fixed to device width and height should be scrollable in the view.

I have managed to get Option 2 to work with this function.

func scaleImageWidth(sourceImage:UIImage, scaledToWidth: CGFloat) -> UIImage {
    let oldWidth = sourceImage.size.width
    let scaleFactor = scaledToWidth / oldWidth

    let newHeight = sourceImage.size.height * scaleFactor
    let newWidth = oldWidth * scaleFactor

    UIGraphicsBeginImageContext(CGSize(width:newWidth, height:newHeight))
    sourceImage.draw(in: CGRect(x:0, y:0, width:newWidth, height:newHeight))
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return newImage!
}

I have modified the function to work for Option 1 like so:

func scaleImageHeight(sourceImage: UIImage, scaledToHeight: CGFloat) -> UIImage {
    let oldheight: CGFloat = sourceImage.size.height
    let scaleFactor: CGFloat = scaledToHeight / oldheight

    let newWidth: CGFloat = sourceImage.size.width * scaleFactor
    let newHeight: CGFloat = oldheight * scaleFactor
    UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
    sourceImage.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))

    let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    return newImage
}

Now both these functions work fine, but in both cases there are some situations where the scaling causes the image to scale smaller than the width or height of the device.

Here are some images of how it looks when the scaling goes wrong.

Actual Result:

enter image description here




Expected Result:

GIF Image of Expected Result

EDITED:

Here are a few images for whose height and width these functions work properly.

Case 1: When Height is greater than width

  1. Width: 1080.0, Height: 2280.0
  2. Width: 1080.0, Height: 2160.0
  3. Width: 1083.0, Height: 1920.0
  4. Width: 1080.0, Height: 2160.0

Case 2: When Width is greater than height

  1. Width: 2715.0, Height: 1920.0
  2. Width: 1945.0, Height: 1920.0
  3. Width: 2278.0, Height: 1920.0

Here are a few images for whose height and width these function produces the above mentioned problem.

Case 1: When Height is greater than width

  1. Width: 1300.0, Height: 1920.0
  2. Width: 1143.0, Height: 1920.0
  3. Width: 1281.0, Height: 1920.0

Case 2: When Width is greater than height

I have not noticed any issues for when width is greater than height. It always produces the right results in this case.


Solution

  • So after a lot of trying. I solved this problem after i read @DonMag answer. Where its mentioned that the issue could be due to not considering the aspect ratio of the image.

    In my scenario I had to have a UIScrollView and have the image fill the scrollView according its width & height. And it had to scroll only in one direction either Vertically or Horizontally based on the aspect ratio of the image.

    To achieve this i used only 1 function from the question above which is OPTION 1 and i modified it as below.

    func scaleImageHeight(sourceImage: UIImage, scaledToHeight: CGFloat) -> UIImage {
        let oldheight: CGFloat = sourceImage.size.height
        let scaleFactor: CGFloat = scaledToHeight / oldheight
    
        let newWidth: CGFloat = sourceImage.size.width * scaleFactor
        let newHeight: CGFloat = oldheight * scaleFactor
        UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
        sourceImage.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
    
        //This check is added as the scaling function would scale down an image to a size which was less than the width of the screen. 
        if newWidth < self.view.frame.width{
            let differenceWidth = self.view.frame.width - newWidth
            newWidth = self.view.frame.width
            newHeight = newHeight+differenceWidth
        }
    
        let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return newImage
    }
    

    So the solution that worked for me, to match the clients requirement was to scale the image always by the height of the image regardless of whether the height or width is greater than the other.

    And after scaling if the width of scaled image was less than screen width then I scale the image back up to the width so that it fits on the screen without any blank spaces. This scaling would result in the height to be scrollable.

    Note:

    1. ImageView should have a contentMode of aspectFill.
    2. This solution may not work for everyone as the images used for scaling here are custom designed images.