在 ios 上使用 AspectFill 截取 UIImageView

Posted

技术标签:

【中文标题】在 ios 上使用 AspectFill 截取 UIImageView【英文标题】:Take screenshot of UIImageView with AspectFill on ios 【发布时间】:2020-08-30 23:59:54 【问题描述】:

我只是想捕获一个 UIImageView 的 UIImage 并将 contentMode 设置为 aspectFill,但有时它不起作用。我也需要它的大小始终为 375 x 667,也许问题与此有关,但我从测试中无法修复它:/

这里是使用的代码:

获取图像:

extension UIView 

    func asImage() -> UIImage 
        let renderer = UIGraphicsImageRenderer(bounds: CGRect(x: 0, y: 0, width: 375, height: 667))
        return renderer.image  rendererContext in
            layer.render(in: rendererContext.cgContext)
        
    


用法:

//ImageView setup stuff
imgViewForVideo.image = thumbnailImage
imgViewForVideo.contentMode = .scaleAspectFill
imgViewForVideo.isHidden = false
                
let newImage = imgViewForVideo.asImage() //usage
    
UIImageWriteToSavedPhotosAlbum(newImage, self, #selector(media(_:didFinishSavingWithError:contextInfo:)), nil) //saving it to phone for testing

这里有 2 个例子来说明我的意思:(无论原始 UIImage 大小如何,图像都应该是纵横比填充并填充整个 375 x 667 屏幕......)

正确填充和截图:

这是一个混乱的例子:(注意:左边的黑色边框不是我的电脑截图错误的问题的一部分。但是它有助于显示屏幕的白色部分......这是我遇到的问题之一......除了图像有时过于放大......)

【问题讨论】:

【参考方案1】:

使用您当前的extension,您是在说:

“在 375 x 667 图像中以 当前大小 渲染视图”

所以,如果您的imgViewForVideo80 x 142(例如以大致相同的纵横比显示“缩略图”),那么您正在这样做:

你想做的是:

当前大小 获取视图的UIImage 并将其缩放 到 375 x 667

您可以通过将 imgViewForVideo 的框架设置为 375 x 667 来做到这一点,或者,要按原样使用图像视图,请使用以下扩展:

extension UIView 
    
    // this method will work, but uses multiple image scaling operations
    // resulting in loss of image quality
    
    func resizedImage(_ size: CGSize, useScreenScale: Bool? = true) -> UIImage 
        let format = UIGraphicsImageRendererFormat()
        if useScreenScale == false 
            format.scale = 1
        
        // use bounds of self
        var renderer = UIGraphicsImageRenderer(bounds: bounds, format: format)
        let img = renderer.image  rendererContext in
            layer.render(in: rendererContext.cgContext)
        
        // use target size
        renderer = UIGraphicsImageRenderer(size: size, format: format)
        return renderer.image  (context) in
            img.draw(in: CGRect(origin: .zero, size: size))
        
    
    

并调用它:

    let targetSZ = CGSize(width: 375, height: 667)
    let newImage = imgViewForVideo.resizedImage(targetSZ, useScreenScale: false)

请注意,该方法最终会多次缩放图像,从而导致质量下降。

更好的方法是使用原始图像并将其缩放并裁剪为您的目标尺寸。

看看这个扩展:

extension UIImage 
    
    // scales and clips original image
    // optionally preserving aspect ratio
    
    func scaleTo(size targetSize: CGSize, mode: UIView.ContentMode? = .scaleToFill, useScreenScale: Bool? = true) -> UIImage 
        // make sure a valid scale mode was requested
        //  if not, set it to scaleToFill
        var sMode: UIView.ContentMode = mode ?? .scaleToFill
        let validModes: [UIView.ContentMode] = [.scaleToFill, .scaleAspectFit, .scaleAspectFill]
        if !validModes.contains(sMode) 
            print("Invalid contentMode requested - using scaleToFill")
            sMode = .scaleToFill
        
        
        var scaledImageSize = targetSize
        
        // if scaleToFill, don't maintain aspect ratio
        if mode != .scaleToFill 
            // Determine the scale factor that preserves aspect ratio
            let widthRatio = targetSize.width / size.width
            let heightRatio = targetSize.height / size.height
            
            // scaleAspectFit
            var scaleFactor = min(widthRatio, heightRatio)
            if mode == .scaleAspectFill 
                // scaleAspectFill
                scaleFactor = max(widthRatio, heightRatio)
            
            
            // Compute the new image size that preserves aspect ratio
            scaledImageSize = CGSize(
                width: size.width * scaleFactor,
                height: size.height * scaleFactor
            )
        
        
        // UIGraphicsImageRenderer uses screen scale, so...
        //  if targetSize is 100x100
        //      on an iPhone 8, for example, screen scale is 2
        //          renderer will produce a 750 x 1334 image
        //      on an iPhone 11 Pro, for example, screen scale is 3
        //          renderer will produce a 1125 x 2001 image
        //
        // if we want a pixel-exact image, set format.scale = 1
        let format = UIGraphicsImageRendererFormat()
        if useScreenScale == false 
            format.scale = 1
        
        
        let renderer = UIGraphicsImageRenderer(
            size: targetSize,
            format: format
        )
        var origin = CGPoint.zero
        if mode != .scaleToFill 
            origin.x = (targetSize.width - scaledImageSize.width) * 0.5
            origin.y = (targetSize.height - scaledImageSize.height) * 0.5
        
        let scaledImage = renderer.image  _ in
            self.draw(in: CGRect(
                origin: origin,
                size: scaledImageSize
            ))
        
        
        return scaledImage
    
    

不要在图像视图上调用“转换为图像”函数,而是直接在图像本身上调用scaleTo(...)

    // make sure the image view has a valid image to begin with
    guard let img = imgViewForVideo.image else 
        print("imgViewForVideo has no image !!!")
        return
    
    
    let targetSZ = CGSize(width: 375, height: 667)
    let newImage = img.scaleTo(size: targetSZ, mode: .scaleAspectFill, useScreenScale: false)

这是一个 2400 x 1500 图像的示例,在 80 x 142 .scaleAspectFill 图像视图中显示在应用内,使用 UIView 扩展名保存为 375 x 667:

这是相同的示例 2400 x 1500 图像,在应用内显示在 80 x 142 .scaleAspectFill 图像视图中,保存为 375 x 667,使用 UIImage 扩展:

这些使用的是原始的 2400 x 1500 图像:

我在这里放了一个示例应用程序(我用来生成这些图像):https://github.com/DonMag/ImageSaveExample

【讨论】:

抱歉我看到这个很晚了;但效果很好!! :)

以上是关于在 ios 上使用 AspectFill 截取 UIImageView的主要内容,如果未能解决你的问题,请参考以下文章

IOS网络篇1之截取本地URL请求(NSURLProtocol)

无法在 UIScrollView 和 AutoLayout 中使用 AspectFill 滚动 UIImageView

UIImageView 设置为 ASpectFill 时的内容模式问题

自定义 UITableViewCell 中的 PFImageView 使用 contentMode“AspectFill”显示不正确

将 UIImageView 与 contentMode AspectFill 和顶部对齐

截取iOS系统返回事件