Search code examples
iosswiftuicollectionviewuikituicollectionviewcompositionallayout

How to use UICollectionViewCompositionalLayout to render a remote image properly in UIKit


I want to display a NSCollectionLayoutItem( contains a image from remote and its aspect ratio is unkown) occupy the device screen horizontally , and still get the correct aspect ratio in Vertical .

So I use the NSCollectionLayoutDimension.estimated(50) points for height . but after the image is downloaded , the height is still 50 ,which is not enough for display the image .

How to make the layout automatically fit the image's content.

the image link is here : [image src] (https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png)

image content is : enter image description here

and the running result will be like this

enter image description here

the UICollectionView configuration code :

import UIKit

class ViewController: UIViewController {
    enum Section {
        case main
    }
    
    @IBOutlet weak var collectionView: UICollectionView!
    var dataSource: UICollectionViewDiffableDataSource<Section, Int>! = nil
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        self.configDataSource()
        self.configLayout()
    }

    
    func configDataSource() {
        let imageCellReg = UICollectionView.CellRegistration<ImageCell,Int>(cellNib: UINib(nibName: "ImageCell", bundle: nil)) { cell, indexPath, itemIdentifier in
            
        }
        self.dataSource = UICollectionViewDiffableDataSource<Section, Int>(collectionView: self.collectionView, cellProvider: { collectionView, indexPath, itemIdentifier in
            return self.collectionView.dequeueConfiguredReusableCell(using: imageCellReg, for: indexPath, item: itemIdentifier)
        })
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, Int>()
        snapshot.appendSections([.main])
        snapshot.appendItems(Array(0..<2))
        dataSource.apply(snapshot, animatingDifferences: false)

    }
    
    func configLayout(){
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                             heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)

        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                               heightDimension:.estimated(50))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize:groupSize, subitems: [item])
        let spacing = CGFloat(10)
        group.interItemSpacing = .fixed(spacing)

        let section = NSCollectionLayoutSection(group: group)
        section.interGroupSpacing = spacing
        section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)

        let layout = UICollectionViewCompositionalLayout(section: section)
        self.collectionView.collectionViewLayout = layout
    }

}

And the cell code:

import UIKit
import SDWebImage

class ImageCell: UICollectionViewCell {
    @IBOutlet weak var imageContentView: UIImageView!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        imageContentView.sd_setImage(with: URL(string: "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"))
    }

}

Solution

  • Very quick example...

    Design your cell like this:

    enter image description here

    We've given the image view a Height constraint -- which will be updated when the image has been downloaded.

    The cell class is as simple as this:

    class ImageCell: UICollectionViewCell {
    
        @IBOutlet var imageContentView: UIImageView!
        @IBOutlet var imgHeight: NSLayoutConstraint!
        
    }
    

    Using SDWebImage, we'll implement a completion block, where we'll calculate the aspect-ratio height for the downloaded image, which we'll use to set the imgHeight.constant. We then call collectionView.collectionViewLayout.invalidateLayout() to tell the controller to re-layout the cells:

    cell.imageContentView.sd_setImage (
        with: URL(string: urlStr),
        placeholderImage: UIImage(named: "somePlaceholder"),
        options: SDWebImageOptions(rawValue: 0),
        completed: { [weak self] image, error, cacheType, imageURL in
            guard let self = self,
                  let img = image
            else { return }
            
            // calculate proportional height
            let iw = cell.imageContentView.frame.width
            let ih = (img.size.height / img.size.width) * iw
            
            // update the height contraint in the cell
            cell.imgHeight.constant = ih
            
            // tell the collection view to re-layout the cells
            self.collectionView.collectionViewLayout.invalidateLayout()
        }
    )
    
            
    

    Here is a complete example project: https://github.com/DonMag/SDWebCV