Unable to simultaneously satisfy constraints. Adaptive cell height using UICollectionViewFlowLayout

71 views Asked by At

I'm trying to make a news feed like app using UICollectionView with adaptive cell height. I found this tutorial and used the option "2. Solution for iOS 11+" for my code, which works perfectly fine. However, whenever I try to add more subviews to the cell and layout them as required, I get this error: "Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want."

Below is my code which gives me an error.

NewsFeedViewController:


import UIKit

class NewsFeedViewController: UIViewController {
    
    var friends: [Friend] = []
    
    var allPosts: [Post?] = []
    
    var shuffledPosts: [Post?] = []
    
    var collectionView: UICollectionView = {
        
        let layout = NewsFeedFlowLayout()
        layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize // new
        layout.scrollDirection = .vertical
        
        let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collection.backgroundColor = UIColor.systemRed
        collection.isScrollEnabled = true
        collection.contentInsetAdjustmentBehavior = .always
        
        collection.translatesAutoresizingMaskIntoConstraints = false
        return collection
    }()
    
    let cellID = "NewsFeedCollectionViewCell"
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        getPosts()
        view.addSubview(collectionView)
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(NewsFeedCollectionViewCell.self, forCellWithReuseIdentifier: cellID)
        collectionView.pin(to: view)
    }
    
    func getPosts() {
        
        friends = FriendFactory().friends
        
        allPosts = friends.flatMap{$0.posts}
        
        var tempPosts = allPosts
        
        for _ in allPosts {
            if let index = tempPosts.indices.randomElement() {
                let post = tempPosts[index]
                shuffledPosts.append(post)
                tempPosts.remove(at: index)
            }
        }
    }
    
}


extension NewsFeedViewController: UICollectionViewDataSource, UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return shuffledPosts.count
    }
    
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! NewsFeedCollectionViewCell
        
        let post = shuffledPosts[indexPath.row]
        let friend = friends.first(where: {$0.identifier == post?.authorID})
        let text = post?.postText

        cell.configurePostText(postText: text!)
        
        return cell
    }
    
}

NewsFeedCollectionViewCell:


import UIKit

class NewsFeedCollectionViewCell: UICollectionViewCell {
    
    var selectedPost = Post()

    var likeBarView: LikeBarView = {
        let view = LikeBarView()
        view.backgroundColor = .systemIndigo

        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    var postTextLabel: UILabel = {
        let label = UILabel()
        label.font = label.font.withSize(20)
        label.numberOfLines = 0
        label.lineBreakMode = .byWordWrapping
        label.backgroundColor = .systemYellow

        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        addViews()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func addViews() {
        addSubview(likeBarView)
        addSubview(postTextLabel)
    }
    
    func configurePostText(postText: String) {
        postTextLabel.text = postText
    }
    
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
    layoutIfNeeded()
    layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
    return layoutAttributes
}
    
    func setupConstraints() {

        NSLayoutConstraint.activate([

            likeBarView.topAnchor.constraint(equalTo: topAnchor, constant: 20),
            likeBarView.leadingAnchor.constraint(equalTo: leadingAnchor),
            likeBarView.trailingAnchor.constraint(equalTo: trailingAnchor),
            likeBarView.heightAnchor.constraint(equalToConstant: 100),

            postTextLabel.topAnchor.constraint(equalTo: likeBarView.bottomAnchor, constant: 20),
            postTextLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
            postTextLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
            postTextLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
                
        ])
    }
}

NewsFeedFlowLayout:

import UIKit

final class NewsFeedFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
        layoutAttributesObjects?.forEach({ layoutAttributes in
            if layoutAttributes.representedElementCategory == .cell {
                if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
                    layoutAttributes.frame = newFrame
                }
            }
        })
        return layoutAttributesObjects
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let collectionView = collectionView else {
            fatalError()
        }
        guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
            return nil
        }

        layoutAttributes.frame.origin.x = sectionInset.left
        layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
        return layoutAttributes
    }

}


I enclose the screenshot of the collection view I'm getting and the screenshot of the analysis of the error done here, which is saying that the height of the cell is not dynamic if I'm getting it right.

What am I doing wrong?

news feed 1

news feed 2

error analysis

1

There are 1 answers

0
Andrey On BEST ANSWER

Adding this line of code to NewsFeedViewController silenced the error:

layout.estimatedItemSize = CGSize(width: 375, height: 200)