0
votes

I'm trying to figure out is it possible for UICollectionView to calculate it's own height using autolayout? My custom cells are built on autolayout and the UICollectionViewFlowLayout.automaticSize property for itemSize seems to be working, but the size of UICollectionView itself should be set. I believe that this is normal behavior, since the collection can have bigger size than it's cells, but maybe it is possible to make height of the content view of UICollectionView to be equal to cell with some insets?

Here is the code for test UIViewController with UICollectionView

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
    
    var collectionView: UICollectionView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .green
        
        let layout = UICollectionViewFlowLayout()
        layout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width, height: 100)
        layout.itemSize = UICollectionViewFlowLayout.automaticSize
        layout.scrollDirection = .horizontal
        layout.minimumLineSpacing = 0
        layout.minimumInteritemSpacing = 0
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)

        
        if let collectionView = collectionView {
            
            collectionView.showsHorizontalScrollIndicator = false
            collectionView.isPagingEnabled = true
            collectionView.register(CollectionViewCell.self,
                                    forCellWithReuseIdentifier: "CollectionViewCell")
            collectionView.backgroundColor = .clear
            collectionView.translatesAutoresizingMaskIntoConstraints = false

            view.addSubview(collectionView)
            collectionView.delegate = self
            collectionView.dataSource = self
            
            NSLayoutConstraint.activate([
                collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
                collectionView.leftAnchor.constraint(equalTo: view.leftAnchor),
                collectionView.rightAnchor.constraint(equalTo: view.rightAnchor),
                // I want to get rid of this constraint
                collectionView.heightAnchor.constraint(equalToConstant: 200)
            ])
        }
    }
    
    
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        5
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell",
                                                            for: indexPath) as? CollectionViewCell else {
            return UICollectionViewCell()
        }
        
        return cell

    }
}

And for custom cell

final class CollectionViewCell: UICollectionViewCell {
    
    var mainView = UIView()
    
    var bigView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .yellow
        return view
    }()
    
    var label: UILabel = {
        let label = UILabel()
        label.text = "Here is text"
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    var anotherLabel: UILabel = {
        let label = UILabel()
        label.text = "Here may be no text"
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    let inset: CGFloat = 16.0
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        addSubview(mainView)
        mainView.translatesAutoresizingMaskIntoConstraints = false
        mainView.backgroundColor = .white
        addSubview(bigView)
        addSubview(label)
        addSubview(anotherLabel)
        
        NSLayoutConstraint.activate([
            mainView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width),
            mainView.topAnchor.constraint(equalTo: topAnchor),
            mainView.leftAnchor.constraint(equalTo: leftAnchor),
            mainView.rightAnchor.constraint(equalTo: rightAnchor),
            mainView.bottomAnchor.constraint(equalTo: bottomAnchor),
            
            bigView.topAnchor.constraint(equalTo: mainView.topAnchor, constant: inset),
            bigView.leftAnchor.constraint(equalTo: mainView.leftAnchor, constant: inset),
            bigView.rightAnchor.constraint(equalTo: mainView.rightAnchor, constant: -inset),
            bigView.bottomAnchor.constraint(equalTo: mainView.bottomAnchor, constant: -inset),
                        
            label.topAnchor.constraint(equalTo: bigView.topAnchor, constant: inset),
            label.leftAnchor.constraint(equalTo: bigView.leftAnchor, constant: inset),
            label.rightAnchor.constraint(equalTo: bigView.rightAnchor, constant: -inset),
            
            anotherLabel.topAnchor.constraint(equalTo: label.bottomAnchor, constant: inset),
            anotherLabel.leftAnchor.constraint(equalTo: bigView.leftAnchor, constant: inset),
            anotherLabel.rightAnchor.constraint(equalTo: bigView.rightAnchor, constant: -inset),
            anotherLabel.bottomAnchor.constraint(equalTo: bigView.bottomAnchor, constant: -inset)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

And maybe someone, who knows the answer also could tell whether it's possible to later put this paging collection into UITalbleViewCell and make size of the cell change with selected item in the collection? I provide screenshot of the collection I'm trying to make: Item's without second text label will have smaller height, than items on the picture

Screenshot

1
Do you mean you want to set collection view size as per your content/Cells?Raja Kishan

1 Answers

0
votes

Solution 1: Get collection view content size from collectionViewLayout :

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
         // Set your collection view height here.
    let collectionHeight = self.yourCollectionView.collectionViewLayout.collectionViewContentSize.height
  }

Solution 2: Use KVO.

// Register observer 
self.yourCollectionView.addObserver(self, forKeyPath: "contentSize", options: [.new, .old, .prior], context: nil)

@objc override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {        
    if keyPath == "contentSize" {
         // content size changed. Set your collection view height here.
let contentSize = change?[NSKeyValueChangeKey.newKey] as? CGSize {
            print("contentSize:", contentSize)

    }
}

// Remove register observer
 deinit {
        self.yourCollectionView.removeObserver(self, forKeyPath: "contentSize")
    }

Solution 3: Assign this class to UICollectionView. If you set height constraint from storyboard then set and enable remove at runtime.

class DynamicCollectionView: UICollectionView {
  override func layoutSubviews() {
    super.layoutSubviews()
    if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
      self.invalidateIntrinsicContentSize()
    }
  }
   
  override var intrinsicContentSize: CGSize {
    var size = contentSize
    size.height += (contentInset.top + contentInset.bottom)
    size.width += (contentInset.left + contentInset.right)
    return size
  }
}