如何在Swift中制作一个可在IBDesignable中使用的图像降采样函数?

huangapple go评论64阅读模式
英文:

How to make an IBDesignable image down sampling function in Swift?

问题

我遇到了一个关于图像内存使用过多的问题,当运行应用程序时,一个 905kb 的 SVG 图像在显示时占用了大约 150mb 的内存,并且该图像被系统缓存。在不同页面加载多个不同的 SVG 图像导致了内存压力问题。

所以我开始研究图像降采样并找到了一个解决方案。我成功创建了一个基于图像大小的降采样函数(得到了帮助)。目前,我可以通过使用一个降采样器类来降采样图像,需要为我想要启用降采样的每个 imageView 设置为自定义类。

但是我正在尝试创建一个 UIImageView 的 IBDesignable 扩展,以便我可以使用任何自定义类,但仍然获得降采样图像的好处。

这是降采样类的代码:

import UIKit

class DownsamplingImageView: UIImageView {
    
    @IBInspectable
    var downSample: Bool = true {
        didSet {
            setNeedsLayout()
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        if downSample, let image = self.image {
            let targetSize = self.bounds.size
            let downsampledImage = image.downsample(to: targetSize)
            self.image = downsampledImage
        }
    }
}

extension UIImage {
    
    func downsample(to targetSize: CGSize) -> UIImage? {
        let sourceSize = self.size
        let widthRatio = targetSize.width / sourceSize.width
        let heightRatio = targetSize.height / sourceSize.height
        let scaleFactor = max(widthRatio, heightRatio)
        
        let scaledSize = CGSize(width: sourceSize.width * scaleFactor, height: sourceSize.height * scaleFactor)
        
        UIGraphicsBeginImageContextWithOptions(scaledSize, false, 0.0)
        self.draw(in: CGRect(origin: CGPoint.zero, size: scaledSize))
        let downsampledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return downsampledImage
    }
}

这是我尝试创建的扩展,但不幸的是它不起作用:

import UIKit
@IBDesignable
extension UIImageView {
    
    @IBInspectable
    var downSample: Bool {
        get {
            return image != nil
        }
        set {
            setNeedsLayout()
        }
    }
    
    override open func layoutSubviews() {
        super.layoutSubviews()
        
        if downSample, let image = self.image {
            let targetSize = self.bounds.size
            let downsampledImage = image.downsample(to: targetSize)
            self.image = downsampledImage
        }
    }
}

extension UIImage {
    
    func downsample(to targetSize: CGSize) -> UIImage? {
        let sourceSize = self.size
        let widthRatio = targetSize.width / sourceSize.width
        let heightRatio = targetSize.height / sourceSize.height
        let scaleFactor = max(widthRatio, heightRatio)
        
        let scaledSize = CGSize(width: sourceSize.width * scaleFactor, height: sourceSize.height * scaleFactor)
        
        UIGraphicsBeginImageContextWithOptions(scaledSize, false, 0.0)
        self.draw(in: CGRect(origin: CGPoint.zero, size: scaledSize))
        let downsampledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return downsampledImage
    }
}

是否有人能帮助或解释为什么 UIImageView 的扩展不起作用,而使用自定义类却起作用?还有没有办法使扩展起作用?

附注:内存使用从 150mb 减少到仅 6mb,并且幸运的是当用户离开页面时它会被释放!如果有人想要启用降采样,第一段代码完美地工作,你只需将 UIImageView 的自定义类设置为 DownsamplingImageView

英文:

I faced and issue with images using too much memory when running the app. A 905kb SVG image was taking around 150mb when presented and this image was being cached by the system. Loading several different SVG images on different pages led to memory pressure issue.

So I started looking into image downsampling and found a solution. I managed create a function to downsample the image based on size of the image (Had help with it).Currently I can downsample the image by using a downsampler class which needs to be set as a custom class for every imageView which I want to enable downsampling.

But I am trying to create an IBDesignable extension for UIImageView so that I can use any custom class but still get the benefit of downsampling the image.

This is the code for downsampling class

import UIKit

class DownsamplingImageView: UIImageView {
    
    @IBInspectable
    var downSample: Bool = true {
        didSet {
            setNeedsLayout()
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        if downSample, let image = self.image {
            let targetSize = self.bounds.size
            let downsampledImage = image.downsample(to: targetSize)
            self.image = downsampledImage
        }
    }
}

extension UIImage {
    
    func downsample(to targetSize: CGSize) -> UIImage? {
        let sourceSize = self.size
        let widthRatio = targetSize.width / sourceSize.width
        let heightRatio = targetSize.height / sourceSize.height
        let scaleFactor = max(widthRatio, heightRatio)
        
        let scaledSize = CGSize(width: sourceSize.width * scaleFactor, height: sourceSize.height * scaleFactor)
        
        UIGraphicsBeginImageContextWithOptions(scaledSize, false, 0.0)
        self.draw(in: CGRect(origin: CGPoint.zero, size: scaledSize))
        let downsampledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return downsampledImage
    }
}

This is how I tried to make an extension but unfortunately it doesn't work.

import UIKit
@IBDesignable
extension UIImageView {
    
    @IBInspectable
    var downSample: Bool {
        get {
            return image != nil
        }
        set {
            setNeedsLayout()
        }
    }
    
    override open func layoutSubviews() {
        super.layoutSubviews()
        
        if downSample, let image = self.image {
            let targetSize = self.bounds.size
            let downsampledImage = image.downsample(to: targetSize)
            self.image = downsampledImage
        }
    }
}

extension UIImage {
    
    func downsample(to targetSize: CGSize) -> UIImage? {
        let sourceSize = self.size
        let widthRatio = targetSize.width / sourceSize.width
        let heightRatio = targetSize.height / sourceSize.height
        let scaleFactor = max(widthRatio, heightRatio)
        
        let scaledSize = CGSize(width: sourceSize.width * scaleFactor, height: sourceSize.height * scaleFactor)
        
        UIGraphicsBeginImageContextWithOptions(scaledSize, false, 0.0)
        self.draw(in: CGRect(origin: CGPoint.zero, size: scaledSize))
        let downsampledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return downsampledImage
    }
}

Could someone please help or explain why the extension on UIImageView doesn't work but using custom class works? Also is there anyway to make the extension work?

PS: 150mb usage reduced to just 6mb usage and fortunately its being released right when user leaves the page !
Just incase anybody want to enable downsampling, the first code works perfectly, all you have to do is set the UIImageView custom class as DownsamplingImageView

答案1

得分: 0

我最终使用了图像降采样器来解决这个问题。

import UIKit

class DownsamplingImageView: UIImageView {
    
    @IBInspectable
    var downSample: Bool = true {
        didSet {
            setNeedsLayout()
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        if downSample, let image = self.image {
            let targetSize = self.bounds.size
            let downsampledImage = image.downsample(to: targetSize)
            self.image = downsampledImage
        }
    }
}

extension UIImage {
    
    func downsample(to targetSize: CGSize) -> UIImage? {
        let sourceSize = self.size
        let widthRatio = targetSize.width / sourceSize.width
        let heightRatio = targetSize.height / sourceSize.height
        let scaleFactor = max(widthRatio, heightRatio)
        
        let scaledSize = CGSize(width: sourceSize.width * scaleFactor, height: sourceSize.height * scaleFactor)
        
        UIGraphicsBeginImageContextWithOptions(scaledSize, false, 0.0)
        self.draw(in: CGRect(origin: CGPoint.zero, size: scaledSize))
        let downsampledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return downsampledImage
    }
}

Credits: 我忘记了我从哪里得到这个代码。我只是回答这个问题以防有人遇到相同的问题。原始所有者获得积分。

英文:

I ended up using an image down sampler to fix this issue.

import UIKit

class DownsamplingImageView: UIImageView {
    
    @IBInspectable
    var downSample: Bool = true {
        didSet {
            setNeedsLayout()
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        if downSample, let image = self.image {
            let targetSize = self.bounds.size
            let downsampledImage = image.downsample(to: targetSize)
            self.image = downsampledImage
        }
    }
}

extension UIImage {
    
    func downsample(to targetSize: CGSize) -> UIImage? {
        let sourceSize = self.size
        let widthRatio = targetSize.width / sourceSize.width
        let heightRatio = targetSize.height / sourceSize.height
        let scaleFactor = max(widthRatio, heightRatio)
        
        let scaledSize = CGSize(width: sourceSize.width * scaleFactor, height: sourceSize.height * scaleFactor)
        
        UIGraphicsBeginImageContextWithOptions(scaledSize, false, 0.0)
        self.draw(in: CGRect(origin: CGPoint.zero, size: scaledSize))
        let downsampledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return downsampledImage
    }
}

Credits : I forgot where I got this from. I am just answering this question incase someone faces the same issue. Credits to original owner.

huangapple
  • 本文由 发表于 2023年6月1日 15:13:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76379485.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定