iosuitableviewscrollfreezealamofireimage

Why is my UITableView scrolling not so smooth for High resolution images


I am having an issue, where I have a set of high resolution images which I display in a UITableView.

I am not doing something right when it comes to caching the images. I also tried and failed the similar implementation with AlamofireImage library and it seems like I find the scrolling is not that smooth in both of the implementations.

However, if I resize the images to a smaller resolution I don't see any problems. But I want to load images with their exact resolutions.

Can anyone tell me what might be the issue?

the code is as follows:

override func viewDidLoad() {
    super.viewDidLoad()

    for i in 1000...2000{
        let url = "https://i.picsum.photos/id/\(i)/3000/2000.jpg";
        imageUrlArray.append(url)
        print(url)
    }
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "ImageCellId") as! ImageTableViewCell


        let imageUrl = imageUrlArray[indexPath.row]

        cell.imgView.image = nil
        cell.tag = indexPath.row

        if let image = imageCache.object(forKey: indexPath.row as AnyObject){
            DispatchQueue.main.async {
                cell.imgView?.image = image as? UIImage
            }
        }

        DispatchQueue.main.async {
            let data = NSData(contentsOf: URL(string: imageUrl)!)
            guard let imageData = data else{
                return
            }
            DispatchQueue.main.async {
                cell.imgView.image = UIImage(data:imageData as Data)
                imageCache.setObject(imageData, forKey: indexPath.row as AnyObject)
                print("Setting object")
            }
        }

        return cell
    }

With Alamofire:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "ImageCellId") as! ImageTableViewCell
    let imageUrl = imageUrlArray[indexPath.row]

    cell.imgView.image = nil
    cell.tag = indexPath.row

    Alamofire.request(imageUrl).responseData { response in

        if case .success(let image) = response.result {
            DispatchQueue.main.async {
                if(cell.tag == indexPath.row){
                    guard let imageData = UIImage(data: image)else{
                        return
                    }

                    cell.imgView.image = imageData
                }
            }
        }
    }
   return cell
  }

Solution

  • Your first attempt with NSData(contentsOf:) on the main thread is will definitely not work well. That is blocking the main thread to perform synchronous network requests. The Alamofire approach is more promising, pushing the requests into an asynchronous process, which should largely alleviate the problem.

    That having been said, using assets that are larger than the control in which you will be using them can result in some stuttering in the UI as large assets are uncompressed and dynamically rescaled within the control. Regardless of the size of images that you’re fetching, you will want to resize them appropriate for your UI. This is easily done with AlamofireImage’s setImage(withURL:).

    For example, in AlamofireImage 4:

    let urls = (1000...2000).compactMap { URL(string: "https://i.picsum.photos/id/\($0)/3000/2000.jpg") }
    let placeholder = UIImage()
    let filter: AspectScaledToFillSizeFilter = {
        let scale = UIScreen.main.scale
        let size = CGSize(width: 50 * scale, height: 50 * scale)
        return AspectScaledToFillSizeFilter(size: size)
    }()
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
    
        let url = urls[indexPath.row]
        cell.customImageView.af.setImage(withURL: url, cacheKey: url.absoluteString, placeholderImage: placeholder, filter: filter, imageTransition: .crossDissolve(0.2), runImageTransitionIfCached: false)
    
        return cell
    }
    

    This downloads the high resolution image, resizes it appropriately for the image view (in this case, my image view is 50×50 points), and does the in-memory caching.


    If you want to prefetch the images, you can specify a prefetch data source:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        tableView.prefetchDataSource = self
    }
    

    And have it download images:

    extension ViewController: UITableViewDataSourcePrefetching {
        func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
            indexPaths.map { urls[$0.row] }.forEach { url in
                let request = URLRequest(url: url)
                let key = url.absoluteString
                UIImageView.af.sharedImageDownloader.download(request, cacheKey: key, filter: filter, completion: nil)
            }
        }
    }