如何根据设备的高度和宽度缩放 UIImage 以适合屏幕

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

我有一个 UIScrollView,里面有一个 UIImage,我从我的服务器上得到的。每个图像的 width 大于 heightheight 大于其 width

我想根据 heightwidth

以适合我的设备屏幕的方式缩放图像

例如: 我从服务器获取图像并检查哪个更大,widthheight

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")
    }                
}

执行此操作后,我想以这两种方式之一缩放图像。

选项 1: 如果 width 大于 height 那么我希望根据固定到设备高度和宽度的 height 来缩放图像,并且宽度应该是 scrollable查看。

选项 2: 如果 height 大于 width 那么我希望根据固定到设备宽度和高度的 width 来缩放图像,并且高度应该是 scrollable查看。

我已经设法让 Option 2 使用此功能。

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!
}

我修改了函数以适用于 Option 1,如下所示:

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
}

现在这两个功能都可以正常工作,但在这两种情况下,在某些情况下缩放会导致图像缩放小于设备的 widthheight

下面是缩放出错时的一些图片。

实际结果:




预期结果:

已编辑:

这里有一些图像,它们的 heightwidth 这些功能可以正常工作。

情况一:当高度大于宽度时

  1. 宽度:1080.0,高度:2280.0
  2. 宽度:1080.0,高度:2160.0
  3. 宽度:1083.0,高度:1920.0
  4. 宽度:1080.0,高度:2160.0

情况二:当宽度大于高度时

  1. 宽度:2715.0,高度:1920.0
  2. 宽度:1945.0,高度:1920.0
  3. 宽度:2278.0,高度:1920.0

这里有一些图像,其 heightwidth 这些函数会产生上述问题。

情况一:当高度大于宽度时

  1. 宽度:1300.0,高度:1920.0
  2. 宽度:1143.0,高度:1920.0
  3. 宽度:1281.0,高度:1920.0

情况二:当宽度大于高度时

我没有注意到宽度大于高度时的任何问题。在这种情况下,它总是会产生正确的结果。

将 UIImageView 约束设置为顶部、左侧、右侧、底部。 现在转到 UIIMageView 的属性检查器并将内容模式 属性 设置为缩放以填充。

在这种情况下,某些图像可能会被拉伸。

我认为问题可能在于您正在比较图像的宽度:高度,但您没有考虑 imageView 的 aspect-ratio。

这是一个 CGSize 扩展,用于根据 "source" 尺寸(图像尺寸)和 [=32] 计算 aspectFitaspectFill 尺寸=] 大小(imageView 大小)。

extension CGSize {
    func aspectFit(sourceSize: CGSize, targetSize: CGSize) -> CGSize {
        let vWidth = targetSize.width / sourceSize.width;
        let vHeight = targetSize.height / sourceSize.height;
        var returnSize = targetSize
        if( vHeight < vWidth ) {
            returnSize.width = targetSize.height / sourceSize.height * sourceSize.width;
        }
        else if( vWidth < vHeight ) {
            returnSize.height = targetSize.width / sourceSize.width * sourceSize.height;
        }
        return returnSize;
    }
    func aspectFill(sourceSize: CGSize, targetSize: CGSize) -> CGSize {
        let vWidth = targetSize.width / sourceSize.width;
        let vHeight = targetSize.height / sourceSize.height;
        var returnSize = targetSize
        if( vHeight > vWidth ) {
            returnSize.width = targetSize.height / sourceSize.height * sourceSize.width;
        }
        else if( vWidth > vHeight ) {
            returnSize.height = targetSize.width / sourceSize.width * sourceSize.height;
        }
        return returnSize;
    }
}

对于您的情况,您想要 aspectFill 尺码。

这是一个简单、完整的示例,将原始计算的输出与 aspectFill 计算的输出进行比较。我使用了您添加到问题中的图像尺寸,以及 375 x 667 的 imageView 尺寸(full-screen on an iPhone 7):

extension CGSize {
    func aspectFit(sourceSize: CGSize, targetSize: CGSize) -> CGSize {
        let vWidth = targetSize.width / sourceSize.width;
        let vHeight = targetSize.height / sourceSize.height;
        var returnSize = targetSize
        if( vHeight < vWidth ) {
            returnSize.width = targetSize.height / sourceSize.height * sourceSize.width;
        }
        else if( vWidth < vHeight ) {
            returnSize.height = targetSize.width / sourceSize.width * sourceSize.height;
        }
        return returnSize;
    }
    func aspectFill(sourceSize: CGSize, targetSize: CGSize) -> CGSize {
        let vWidth = targetSize.width / sourceSize.width;
        let vHeight = targetSize.height / sourceSize.height;
        var returnSize = targetSize
        if( vHeight > vWidth ) {
            returnSize.width = targetSize.height / sourceSize.height * sourceSize.width;
        }
        else if( vWidth > vHeight ) {
            returnSize.height = targetSize.width / sourceSize.width * sourceSize.height;
        }
        return returnSize;
    }
}

class TestViewController: UIViewController {

    let testSizes: [CGSize] = [

        // width > height
        CGSize(width: 2715.0, height: 1920.0),
        CGSize(width: 1945.0, height: 1920.0),
        CGSize(width: 2278.0, height: 1920.0),

        // used to provide a blank line in debug output
        CGSize.zero,

        // height > width
        CGSize(width: 1080.0, height: 2280.0),
        CGSize(width: 1080.0, height: 2160.0),
        CGSize(width: 1083.0, height: 1920.0),
        CGSize(width: 1080.0, height: 2160.0),

        // used to provide a blank line in debug output
        CGSize.zero,

        // height > width problems
        CGSize(width: 1300.0, height: 1920.0),
        CGSize(width: 1143.0, height: 1920.0),
        CGSize(width: 1281.0, height: 1920.0),

    ]

    let imageViewSize = CGSize(width: 375, height: 667)

    override func viewDidLoad() {
        super.viewDidLoad()

        print()

        testSizes.forEach { sz in
            if sz == CGSize.zero {
                print()
            } else {
                // original size calcs
                if sz.height > sz.width {
                    let newSZ = scaleImageWidth(sourceImageSize: sz, scaledToWidth: imageViewSize.width)
                    print("Orig: \(newSZ)")
                } else {
                    let newSZ = scaleImageHeight(sourceImageSize: sz, scaledToHeight: imageViewSize.height)
                    print("Orig: \(newSZ)")
                }
                // aspectFill calc
                let newSZ = sz.aspectFill(sourceSize: sz, targetSize: imageViewSize)
                print("Fill: \(newSZ)")
            }
        }

        print()

    }

    func scaleImageWidth(sourceImageSize: CGSize, scaledToWidth: CGFloat) -> CGSize {
        let oldWidth = sourceImageSize.width
        let scaleFactor = scaledToWidth / oldWidth

        let newHeight = sourceImageSize.height * scaleFactor
        let newWidth = oldWidth * scaleFactor

        return CGSize(width: newWidth, height: newHeight)
    }

    func scaleImageHeight(sourceImageSize: CGSize, scaledToHeight: CGFloat) -> CGSize {
        let oldheight: CGFloat = sourceImageSize.height
        let scaleFactor: CGFloat = scaledToHeight / oldheight

        let newWidth: CGFloat = sourceImageSize.width * scaleFactor
        let newHeight: CGFloat = oldheight * scaleFactor

        return CGSize(width: newWidth, height: newHeight)
    }

}

这是您应该在调试控制台中获得的内容:

Orig: (943.1796875, 667.0)
Fill: (943.1796875, 667.0)
Orig: (675.6848958333334, 667.0)
Fill: (675.6848958333334, 667.0)
Orig: (791.3677083333333, 667.0)
Fill: (791.3677083333333, 667.0)

Orig: (375.0, 791.6666666666666)
Fill: (375.0, 791.6666666666666)
Orig: (375.0, 750.0)
Fill: (375.0, 750.0)
Orig: (375.0, 664.819944598338)
Fill: (376.2296875, 667.0)
Orig: (375.0, 750.0)
Fill: (375.0, 750.0)

Orig: (374.99999999999994, 553.8461538461538)
Fill: (451.61458333333337, 667.0)
Orig: (375.0, 629.9212598425197)
Fill: (397.0734375, 667.0)
Orig: (375.0, 562.0608899297424)
Fill: (445.0140625, 667.0)

如您所见,您发布的 "problematic" 尺码明显不同 return 尺码。

经过大量尝试。我在阅读 @DonMag 答案后解决了这个问题。它提到问题可能是由于未考虑图像的 aspect ratio 引起的。

在我的场景中,我必须有一个 UIScrollView 并让图像根据其 widthheight 填充 scrollView。而且它只能根据图像的纵横比 VerticallyHorizontally 向一个方向滚动。

为了实现这一点,我只使用了上述问题中的 1 个函数,即 OPTION 1 并且我将其修改如下。

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
}

因此,为了满足客户的要求,对我有用的解决方案是始终按图像的 height 缩放图像,而不管 height 还是 width 是一个比一个大。

如果缩放后​​的图像宽度小于屏幕宽度,我会在缩放后将图像缩放回 width,以便它适合屏幕,没有任何空格。此缩放将导致 height 可滚动。

注:

  1. ImageView 应该有一个 contentModeaspectFill
  2. 此解决方案可能不适用于所有人,因为此处用于缩放的图像是自定义设计的图像。