swift 第一次加载时URL中的可扩展ViewCell图像错误

xjreopfe  于 2023-09-30  发布在  Swift
关注(0)|答案(1)|浏览(124)

情况

将url image设置为UITableViewCell有问题。我创建了CancelableImageView,这是自定义的UIImageView,用于在设置新图像时取消图像下载和设置任务。通过使用此方法,“错误图像”问题似乎得到了解决,但这仍然发生在UITableView上

加载UITableView后,一切正常,但在第一个单元格加载完图片并更改图片后,第四个单元格的图片也变成了第一个单元格的图片。并且第四单元格的图像在滚动之后改变为正确的图像。

代码

这是我的图片加载扩展为UIImage(包含图像缓存)

/// Singleton for Image Caching
class ImageCacheManager {
    
    /// Storage to save cached UIImage
    private let cache = Cache<String, UIImage>()
    
    /// singleton instance
    static let shared = ImageCacheManager()
    
    /**
    Get image from cache data for url String
    - Parameter key:  String url of UIImage
    - Returns: Retrun cached image for url. Retrun nil when cached image is not exist
    */
    func loadCachedData(for key: String) -> UIImage? {
        let itemURL = NSString(string: key)
        return cache.value(forKey: key)
    }
    
    /**
    Save UIImage to cache data
     - Parameters:
        - image: UIImage to save in cache data
        - key:  String url of UIImage
     */
    func saveCacheData(of image: UIImage, for key: String) {
        let itemURL = NSString(string: key)
        cache.insertValue(image, forKey: key)
    }
}

extension UIImageView {

    /**
     Set image to UIImageView with Cached Image data or data from URL
     - Parameters:
        - urlString: String url of image
        - forceOption: Skip getting image from cache data and force to get image from url when true. default false
     */
    func loadImage(_ urlString: String?, forceOption: Bool = false) -> UIImage? {
        guard let imagePath = urlString else { return nil }
        
        // Cached Image is available
        if let cachedImage = ImageCacheManager.shared.loadCachedData(for: imagePath), forceOption == false {
            return cachedImage
            
            // No Image Cached
        } else {
            guard let imageURL = URL(string: imagePath) else { return nil }
            guard let imageData = try? Data(contentsOf: imageURL) else { return nil }
            guard let newImage = UIImage(data: imageData) else { return nil }
            
            ImageCacheManager.shared.saveCacheData(of: newImage, for: imagePath)
            return newImage
        }
    }
    
    func setImage(_ urlString: String?, forceOption: Bool = false) {
        DispatchQueue.global().async {
            guard let image = self.loadImage(urlString, forceOption: forceOption) else { return }
            
            DispatchQueue.main.async {
                self.image = image
            }
        }
    }
}

这是自定义UIImage,在设置新图像时取消图像加载任务:

/// UIImageView with async image loading functions
class CancelableImageView: UIImageView {
    
    /// Cocurrent image loading work item
    private var imageLoadingWorkItem: DispatchWorkItem?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    init() {
        super.init(frame: CGRect.zero)
    }
    
}

// MARK: Concurrent image loading method
extension CancelableImageView {
    
    /**
     Cancel current image loading task and Set image to UIImageView with Cached Image data or data from URL
     - Parameters:
        - urlString: String url of image
        - forceOption: Skip getting image from cache data and force to get image from url when true. default false
     */
    func setNewImage(_ urlString: String?, forceOption: Bool = false) {
        
        self.cancelLoadingImage()
        self.imageLoadingWorkItem = DispatchWorkItem { super.setImage(urlString, forceOption: forceOption) }
        self.imageLoadingWorkItem?.perform()
    }
    
    /// Cancel current image loading work
    func cancelLoadingImage() {
        DispatchQueue.global().async {
            self.imageLoadingWorkItem?.cancel()
        }
    }
}

这是我的UITableViewCell的视图:

class ChartTableViewCell: UITableViewCell {

    lazy var posterImageView = CancelableImageView().then {
        $0.contentMode = .scaleAspectFit
        $0.image = UIImage(named: "img_placeholder")
    }
    
    ... 

    override func prepareForReuse() {
        setPosterPlaceholderImage()
        super.prepareForReuse()
    }
}

extension ChartTableViewCell {
    
    /// Set poster imageView placeholder Image
    private func setPosterPlaceholderImage() {
        self.posterImageView.image = UIImage(named: "img_placeholder")
    }
    
    // MARK: Set Data
    func setData(rank: Int, movie: MovieFront) {
        
        rankLabel.text = "\(rank+1)"
        titleLabel.text = movie.title
        genreLabel.text = movie.genre
        releaseDateLabel.text = movie.releaseDate
        starRating.rating = movie.ratingScore/2
        ratingCountLabel.text = "(\(movie.ratingCount))"
        if let imagePath = movie.posterPath {
            posterImageView.setNewImage(APIService.configureUrlString(imagePath: imagePath))
        }
    }
}

以下是UITableViewDataSource(cellForRowAt):

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        guard let cell = tableView.dequeueReusableCell(withIdentifier: identifiers.chart_table_cell) as? ChartTableViewCell else {
            return UITableViewCell()
        }
        
        cell.setData(rank: indexPath.row, movie: viewModel.movieListData.value[indexPath.row])
        return cell
    }
s4n0splo

s4n0splo1#

几个观察结果:
1.在setImage中,异步self.image = image是有问题的,因为您不知道单元格(以及图像视图)是否已被表/集合视图中的另一行/项重用。我知道你认为你已经取消了,但你没有。见下文第2点。
一般的解决方案(在UIImageView扩展中)是为URL提供一个“关联值”(例如,objc_setAssociatedObject(_:_:_:_:)等),并确保此特定图像视图的URL仍然是您在更新image属性之前异步获取的URL。如果你真的想使用子类的方法,只需要将它存储在子类的某个属性中(并完全失去UIImageView扩展)。优雅的解决方案是没有任何图像视图子类,但将其放置为UIImageView扩展,并使用“关联值”来表示您需要跟踪特定图像视图示例的任何其他值。
1.您正在取消调度工作项,但实际上并没有取消请求。(取消是“合作的”,而不是“抢先的”。)更糟糕的是,你甚至没有使用一个可取消的网络请求。
我建议使用URLSession并保存URLSessionTask,而不是使用分派工作项和Data(contentsOf:)。可以取消的。同样,我们将使用“关联值”来保存旧的URLSessionTask,以防您需要取消它。请参见https://stackoverflow.com/a/45183939/1271826
1.当您启动异步图像检索时,请确保将图像重置回占位符,以防有问题的单元格被重用。我看到您在第一次创建图像视图时设置了占位符,但在为重用的单元格获取新图像时却没有设置。
在充分披露,以上是关于修复图像视图的异步图像检索/缓存功能中的问题的一般观察,但您可能还有另一个问题。具体来说,上面的方法修复了在视图中滚动单元格时出现的问题,但您的动画gif显示了另一个问题,即更新第一个单元格的图像似乎是更新第四个单元格的图像。这可能是上面第三个要点的表现,也可能是其他原因。没有足够的证据来诊断它。我建议解决上述问题,如果你仍然有问题,然后发布一个新的问题与MRE,以帮助进一步诊断这一点。
但是,坦率地说,我可能会鼓励您考虑建立异步图像检索和缓存库,如KingFisher、SDWebImage、AlamofireImage等。所有这一切都足够复杂,谨慎的做法可能是不要“重新发明轮子”。

相关问题