在iOS Swift中下载SVG图像

8
我正在处理一个项目,从API获取数据,在该API中,我得到了图像的URL。但是这些图像不是PNG、JPEG或JPG格式,它们是SVG(可缩放矢量图形)图像。因此,据我所知,没有直接的方法、函数或可能用于呈现SVG图像的iOS项目框架库存在。因此,我们有一堆Pods,实际上用于渲染这些图像,例如(SDWebImage、SDWebImageSVGCoder、SDWebImageSVGKitPlugin、SVGKit)。我使用了它们所有,但所有这些Pod都没有解决我的问题。让我们讨论一下我现在面临的问题。
以下是我的ViewController类代码。
import SDWebImageSVGCoder

class ViewController: UIViewController {
    
    var banks: [Bank] = [Bank(logo:"https://identity.moneyhub.co.uk/bank-icons/default",name:"Dummy"),
        Bank(logo: "https://identity.moneyhub.co.uk/bank-icons/accord", name: "Accord Mortgages"),
                           Bank(logo: "https://identity.moneyhub.co.uk/bank-icons/ajBell", name: "AJ Bell"),
                           Bank(logo: "https://identity.moneyhub.co.uk/bank-icons/aldermore", name: "Aldermore"),
                           Bank(logo: "https://identity.moneyhub.co.uk/bank-icons/amex", name: "American Express"),
                           Bank(logo: "https://identity.moneyhub.co.uk/bank-icons/brewinDolphin", name: "Brewin Dolphin"),
                           Bank(logo: "https://identity.moneyhub.co.uk/bank-icons/caxton", name: "Caxton"),]
    
    
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
    }
    
    func setupTableView() {
        self.tableView.delegate = self
        self.tableView.dataSource = self
    }

}

//MARK: - TABLEVIEW DELEGATE DATASOURCE
extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return banks.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: BankTableViewCell.identifier, for: indexPath) as! BankTableViewCell
        cell.configure(self.banks[indexPath.row])
        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 62
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
    }
    
}

我的TableViewCell类
class BankTableViewCell: UITableViewCell {
    
    @IBOutlet weak var bankNameLabel: UILabel!
    @IBOutlet weak var bankLogoImageView: UIImageView!
    
    class var identifier: String {
        return "BankTableViewCell"
    }
    
    func configure(_ bank: Bank) {
        self.bankNameLabel.text = bank.name
        guard let url = URL(string: bank.logo) else {return}
        self.bankLogoImageView.sd_setImage(fromURL: url)
    }
    
}

但是当我运行这段代码时,只有3张图片被渲染出来,第一张是虚拟或默认情况,但其余三张未被渲染。然后我尝试查找问题所在,发现并不是所有的图片都是真正的SVG图片,有些实际上是光栅图像。下面是我分享的两种图像类型的数据。
成功渲染的那些图片的数据如下:
<svg viewBox="-25 -25 200 200" xmlns="http://www.w3.org/2000/svg">
    <path d="M138.299 130.707H10.644a6.386 6.386 0 00-6.384 6.391 6.384 6.384 0 006.384 6.384h127.652a6.384 6.384 0 006.384-6.384c-.002-3.532-2.86-6.391-6.381-6.391zM18.621 114.101c-3.526 0-6.384 2.859-6.384 6.388s2.858 6.391 6.384 6.391h111.697c3.526 0 6.384-2.861 6.384-6.391s-2.858-6.388-6.384-6.388h-1.593V56.625h1.593a3.19 3.19 0 000-6.38H18.621a3.19 3.19 0 000 6.38h1.597v57.472h-1.597v.004zm97.336-57.476v57.472H96.81V56.625h19.147zm-31.917 0v57.472H64.893V56.625H84.04zm-51.06 0h19.147v57.472H32.98V56.625zM10.644 44.503H138.34a6.387 6.387 0 006.402-6.388 6.392 6.392 0 00-4.312-6.044L77.094 3.558a6.4 6.4 0 00-5.237 0L8.026 32.293a6.385 6.385 0 00-3.622 7.165 6.38 6.38 0 006.24 5.045z"/>
</svg>

但那些未渲染但显示白色或空图像的图像具有此类型的数据。
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="20" y="135" width="460" height="230" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0" transform="scale(0.005 0.01)"/>
</pattern>
<image id="image0" width="200" height="100" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAACBRJREF......UwT2kAAAAASUVORK5CYII="/>
</defs>
</svg>

我的屏幕在运行项目后看起来像这样。

List of banks

我自己找到了解决方案,只需添加条件并首先检查每个图像响应。如果图像是纯 SVG 格式,那么我就调用 Pod 函数;否则,我就使用自己的自定义代码来提取 base64 编码字符串,然后渲染图像。但这会导致一些性能问题。正如您所知,该库也在异步调用中工作,我也在使用它,因此图像有时会加载,有时不会正确显示。

我的自定义代码在这里:

class BankTableViewCell: UITableViewCell {
    
    @IBOutlet weak var bankNameLabel: UILabel!
    @IBOutlet weak var bankLogoImageView: SVGImageView!
    
    class var identifier: String {
        return "BankTableViewCell"
    }
    
    func configure(_ bank: Bank) {
        self.bankNameLabel.text = bank.name
        guard let url = URL(string: bank.logo) else {return}
        self.bankLogoImageView.loadImage(fromURL: url, placeHolderImage: "")
    }
    
}

class SVGImageView: UIImageView {
    
    private let imageCache = NSCache<AnyObject, UIImage>()
    
    func loadImage(fromURL imageURL: URL, placeHolderImage: String)
    {
        self.image = UIImage(named: placeHolderImage)

        if let cachedImage = self.imageCache.object(forKey: imageURL as AnyObject)
        {
            debugPrint("image loaded from cache for =\(imageURL)")
            self.image = cachedImage
            return
        }

        DispatchQueue.global().async {
            [weak self] in
            
            if let imageData = try? Data(contentsOf: imageURL) {
                guard let xmlStr = String(data: imageData, encoding: .utf8) else {
                    DispatchQueue.main.async {
                        self?.sd_setImage(with: URL(string: "https://identity.moneyhub.co.uk/bank-icons/default")!)
                    }
                    return
                }
                print(xmlStr)
                if let range = xmlStr.range(of: "data:image/png;base64,") {
                    let base64StringWithoutfilter = xmlStr.suffix(from: range.upperBound)
                    let base64StringWithoutfilterRange = base64StringWithoutfilter.range(of: "/>")
                    
                    let lowerRange = range.upperBound
                    if let upperRange = base64StringWithoutfilterRange?.upperBound {
                        let base64ImageString = xmlStr[lowerRange..<upperRange].dropLast(3)
                        if let imageData = NSData(base64Encoded: String(describing:base64ImageString)) {
                            if let image = UIImage(data: imageData as Data) {
                                DispatchQueue.main.async {
                                    self?.image = image
                                    return
                                }
                            }
                        }
                    }
                } else {
                    self?.sd_setImage(with: imageURL,completed: { image, error,cache, url in
                        if let image = image {
                            DispatchQueue.main.async {
                                self?.image = image
                                return
                            }
                        }
                    })
                }
            }
        }
    }
}

我只想解决把我的自定义代码放入库中的问题,因为我不懂Objective C,而SDWebImageSVGCoder pod是用Objective C编写的。如果这不可能,请给我一个建议,或者任何其他库pod,任何可以解决我的问题并显示我的列表看起来像这样的东西。

enter image description here


你尝试过先放置defs吗? - Robert Longson
1
我会创建一个自定义的 SDImageCoder,它将执行您的自定义解码,如果不是自定义的,则使用默认的 SVG 解码器。将其设置在 context: [.imageCoder : YourCustomDecoder] 参数中。如果没有,请按建议调用 sd_setImage(),并在完成后,如果有错误,请测试您的方法。 - Larme
Robert Longson,defs是什么?能否给我一些建议或代码片段。谢谢。 - Murtaza Mehmood
@Larme 我之前不知道Objective C,否则我会把我的代码放到SDWebImage的pod类中。我尝试调试pod类的代码,但是大部分内容都看不懂。如果你能在这里发布一些代码片段,那就太好了。谢谢。 - Murtaza Mehmood
1个回答

4

我会使用自定义解码器。完成后,您可以使用以下代码调用它:

self.bankLogoImageView.sd_setImage(with: url,
                                   placeholderImage: nil,
                                   context: [.imageCoder: CustomSVGDecoder(fallbackDecoder: SDImageSVGCoder.shared)])

我将其命名为CustomSVGDecoder,但由于它是PNG/JPG解码器,可能有更好的名称。我没有检查,但我猜想,你得到的自定义SVG使用了一些SVG功能,并将非矢量图像并入其中,但目前你只使用了非矢量图像。

我为其提供了默认的SDWebImageSVGDecoder的备用解码器,以避免检查。

class CustomSVGDecoder: NSObject, SDImageCoder {

    let fallbackDecoder: SDImageCoder?

    init(fallbackDecoder: SDImageCoder?) {
        self.fallbackDecoder =  fallbackDecoder
    }

    static var regex: NSRegularExpression = {
        let pattern = "<image.*xlink:href=\"data:image\\/(png|jpg);base64,(.*)\"\\/>"
        let regex = try! NSRegularExpression(pattern: pattern, options: [])
        return regex
    }()

    func canDecode(from data: Data?) -> Bool {
        guard let data = data, let string = String(data: data, encoding: .utf8) else { return false }
        guard Self.regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) == nil else {
            print("CustomSVGDecoder: We can decode")
            return true //It self should decode
        }
        guard let fallbackDecoder = fallbackDecoder else {
            print("CustomSVGDecoder: Can't decode and there is no fallBack decoder")
            return false
        }
        print("CustomSVGDecoder: Will rely on fallback decoder to decode")
        return fallbackDecoder.canDecode(from: data)
    }

    func decodedImage(with data: Data?, options: [SDImageCoderOption : Any]? = nil) -> UIImage? {
        guard let data = data,
                let string = String(data: data, encoding: .utf8) else { return nil }
        guard let match = Self.regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) else {
            print("CustomSVGDecoder: Will rely on fallback decoder to decode because of no match")
            return fallbackDecoder?.decodedImage(with: data, options: options)
        }
        guard let rawBase64DataRange = Range(match.range(at: 2), in: string) else {
            print("CustomSVGDecoder: Will rely on fallback decoder to decode because we didn't fiund the base64 part")
            return fallbackDecoder?.decodedImage(with: data, options: options)
        }
        let rawBase64Data = String(string[rawBase64DataRange])
        guard let imageData = Data(base64Encoded: Data(rawBase64Data.utf8), options: .ignoreUnknownCharacters) else {
            print("CustomSVGDecoder: Will rely on fallback decoder to decode because of invalid base64")
            return fallbackDecoder?.decodedImage(with: data, options: options)
        }
        return UIImage(data: imageData)
    }

    //You might need to implement these methods, I didn't check their meaning yet
    func canEncode(to format: SDImageFormat) -> Bool {
        print("CustomSVGDecoder: canEncode(to:)")
        return true
    }

    func encodedData(with image: UIImage?, format: SDImageFormat, options: [SDImageCoderOption : Any]? = nil) -> Data? {
        print("CustomSVGDecoder: encodedData(with:format:options:)")
        return nil
    }
}

我使用正则表达式来查找Base64内容,可能需要进行一些修改。你可以使用自己的版本和range(of:)一起使用。


它已经在工作了。非常感谢。我被这个问题困扰了两个多星期。 - Murtaza Mehmood

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接