Self-sizing UICollectionView cells with async imag

2019-07-26 19:44发布

I have a horizontal UICollectionView in which I display images that are loaded asynchronously. The cells are supposed to have their width fit the image.

In viewDidLoad of my view controller, I set the estimated cell size:

(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.estimatedItemSize = CGSize(width: 400, height: 400)

In cellForItem, I start the download task using Kingfisher:

cell.imageView.kf.setImage(with: url) { (_, _, _, _) in 
    cell.layoutSubviews()
}

Inside my cell, I have the following code inside the layoutSubviews method:

// 326 is the image view's height
// .aspectRatio is a custom extension that returns: size.width / size.height
imageViewWidthConstraint.constant = 326 * image.aspectRatio
layoutIfNeeded()

In the storyboard, I have properly setup the layout constraints so that imageViewWidthConstraint is respected for the cell's width.

The following is the result when running my app:

wrong cell widths

As you can see, the cells have a width of 400, although the image was loaded and the layout updated. And, as result, the images are stretched to fill the image view.
When I scroll to the right & then back, the cells are removed from the collection view and loaded back in, and are now properly laid out:

right cell widths

While scrolling, the cells adjust their width, sometimes to the correct width, sometimes to the wrong one.

What am I doing wrong here?

1条回答
时光不老,我们不散
2楼-- · 2019-07-26 20:21

Since your images come in asynchronously it may take some time to be loaded. Only once they are loaded you can actually know the size or ratio of the image. That means for every image that is loaded you need to "reload" your layout. From a short search this looks promising.

At least this way you may get some animations, otherwise your cells will just keep jumping when images start to be loaded.

In any case I would advise you to avoid this. If I may assume; you are getting some data from server from which you use delivered URLs to download the images and show them on the collection view. The best approach (if possible) is to request that the API is extended so that you receive dimensions of images as well. So instead of

{
    id: 1,
    image: "https://..."
}

You could have

{
    id: 1,
    image: {
        url: "https://...",
        width: 100,
        height: 100
    } 
}

You can now use these values to generate aspect ratio width/height before you download the images.

Next to that I don't really see any good solution for the whole thing to look nice (without jumping around).

查看更多
登录 后发表回答