1

I have an array of strings containing the URL of the photo images. I have a Custom Layout and I need to convert an array of strings to UIImage, and then get their size. Here is my code:

extension PhotoFeedViewController: CustomLayoutDelegate {

func collectionView(_ collectionView: UICollectionView, sizeOfPhotoAtIndexPath indexPath: IndexPath) -> CGSize {

    let bodyImage = UIImageView()
    let url = URL(string: collectionPhotos[indexPath.row])

    let data = try? Data(contentsOf: url!)
//        DispatchQueue.main.async {
        if let imageData = data {
            bodyImage.image = UIImage(data: imageData)
        }
//        }

    return bodyImage.image?.size ?? CGSize(width: 100, height: 100)
   }
}

This code works , but there is a problem. When I launch the app, the images appear after all the images are loaded, because everything is synchronous.

If you uncomment:

DispatchQueue.main.async {

The returned property is empty and does not see the code that is in the Dispatch block

How do I make this code asynchronous so that the size of the images is passed asynchronously and the user doesn't wait for the full load of images to be displayed on the screen? Without the use of libraries.

Sorry for my English.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Does this answer your question? [Loading/Downloading image from URL on Swift](https://stackoverflow.com/questions/24231680/loading-downloading-image-from-url-on-swift) – dahiya_boy Mar 08 '20 at 15:56
  • It would be better if you use a fixed size for all your images. Another option is to struct your data adding the image size to it and the image url this way you don’t need to wait downloading your image data to know its size. – Leo Dabus Mar 08 '20 at 15:59
  • @dahiya_boy - Most of those answers are outdated and/or advocating sub-optimal patterns. Plus this question has a whole other layer of complexity above and beyond that (namely, Nikita is downloading the image just to figure out the size, not saving the downloaded image, not considering that the image may have previously been downloaded, etc.). – Rob Mar 08 '20 at 16:36

1 Answers1

1

You’re absolutely right that synchronous requests are a problem. (And we’d often use URLSession, not just wrap it in an async call.)

But you may have to fundamentally rethink this process. You really don’t want to download an image so you can determine its size, just so that can re-download it again in cellForItemAt. (And I wouldn’t suggest relying on URLCache to save you.)

Ideally, if the source of this array of URLs could return the size of the images at the same time, and would cut the Gordian knot. If you had a list of URLs and the respective image sizes, that would eliminate all of the issues discussed below.

Failing that, you are down to two fundamental alternatives:

  1. Do you want to just force all of the images to be the same size in the UI regardless of the size of the actual image (e.g. and use “aspect scale fill” content mode)? That would be easiest.

  2. Do you want to assume some standard size until the image is downloaded and then reload that particular cell when the respective image download is done (possibly causing re-layout of everything)? Since you can’t always control the order that asynchronous images are returned, this can sometimes result in jarring changes in the collection view as they dribble in.

This latter approach has lots of little complications:

  • Write a download manager that keeps track of downloaded images caching previous downloads (ideally, both in memory and persistent storage). By using a cache, if you scroll a cell into view, it would check to see if you downloaded the image already and if so, use that. E.g. when you scroll back up in a collection view, you don’t want to initiate network requests for images that were previously downloaded.

  • In your asynchronous image retrieval process, you wouldn’t just update the image view, but rather you’d completely reload the cell associated with the IndexPath for that cell. This will trigger it to inquire about the size of the image again.

  • So “size for this cell” routine would effectively say “do I have image downloaded? if so return its size; if not initiate asynchronous download (which will reload the cell when its done) and return some placeholder size.” There would be similar logic in cellForItemAt.

A few other considerations:

  • You are returning the size of the image. I don’t know how you’re using this method, but often we’d consider the scale of the device before figuring out the size of image view (in points) based upon the size of the image (generally in pixels).

  • If you’re not doing it already, when you solve the above issues, you might want to implement prefetching. This will diminish any jarring resizing of cells if the image is already downloaded by the time you scroll to a particular content offset within the collection view.

Rob
  • 415,655
  • 72
  • 787
  • 1,044