0

I am attempting to size the height of the UICollectionViewCell according to the height of the text. For some odd reason, the height of the text is extremely gibberish. I adopted various solutions in the posts here, here and here but all somehow did not work in my case.

My approach involves autolayout which I do not think is a contributing factor. My code so far:

class ChatBubbleCollectionViewCell: UICollectionViewCell {
    
    let textView: UITextView = {
        let tv = UITextView()
        tv.translatesAutoresizingMaskIntoConstraints = false
        tv.backgroundColor = .lightGray
        tv.font = UIFont.systemFont(ofSize: 17)
        tv.isScrollEnabled = false
        tv.isEditable = false
        tv.sizeToFit()
        tv.contentInset.top = -4
        return tv
    }()
    
    let bubbleView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.layer.cornerRadius = 8
        return v
    }()
    
    var bubbleWidthAnchor: NSLayoutConstraint!
    var bubbleLeftAnchor: NSLayoutConstraint!
    var bubbleRightAnchor: NSLayoutConstraint!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        setupViews()
        
        backgroundColor = .blue
    }
    
    func setupViews() {
        addSubview(bubbleView)
        addSubview(textView)
        
        bubbleView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        bubbleView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        
        let viewWidth = UIScreen.main.bounds.width
        
        bubbleWidthAnchor = bubbleView.widthAnchor.constraint(equalToConstant: viewWidth * 0.8)
        bubbleWidthAnchor.isActive = true
        
        bubbleLeftAnchor = bubbleView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8)
        bubbleLeftAnchor.isActive = false
        
        bubbleRightAnchor = bubbleView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -8)
        bubbleRightAnchor.isActive = true

        textView.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 4).isActive = true
        textView.leftAnchor.constraint(equalTo: bubbleView.leftAnchor, constant: 4).isActive = true
        textView.rightAnchor.constraint(equalTo: bubbleView.rightAnchor, constant: -4).isActive = true
        textView.bottomAnchor.constraint(equalTo: bubbleView.bottomAnchor, constant: -4).isActive = true
    }
    
//At UICollectionViewController
 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ChatBubbleCollectionViewCell
        let item = messages[indexPath.item]
        
        if item.fromUserUID == fromUserUID {
            cell.bubbleView.backgroundColor = .customPink
            cell.textView.text = item.message
            cell.textView.textColor = .white
            cell.bubbleLeftAnchor.isActive = false
            cell.bubbleRightAnchor.isActive = true
        } else {
            cell.bubbleView.backgroundColor = .faintGray
            cell.textView.text = item.message
            cell.textView.textColor = .black
            cell.bubbleLeftAnchor.isActive = true
            cell.bubbleRightAnchor.isActive = false
        }
        
        cell.bubbleWidthAnchor.constant = estimatedFrame(for: item.message!).width 

        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        var height: CGFloat = 0
        
        if let text = messages[indexPath.item].message {
            height = ceil(estimatedFrame(for: text).height)
        }
        
        return CGSize(width: view.frame.width, height: height)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 10
    }
    
    func estimatedFrame(for text: String) -> CGRect {
        let screenWidthDownsize = UIScreen.main.bounds.width * 0.8
        let size = CGSize(width: screenWidthDownsize, height: CGFloat.greatestFiniteMagnitude)
        let boundingBox = NSString(string: text).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 17)], context: nil)
        return boundingBox
    }

The above code gives the following results:

enter image description here

And if I add 50 to the height to expand the cell, like so height = ceil(estimatedFrame(for: text).height) + 50 the results give:

enter image description here

From the images, I suppose one can observe that the boundingBox isn't really giving the right values? What can I try next?

halfer
  • 19,824
  • 17
  • 99
  • 186
Koh
  • 2,687
  • 1
  • 22
  • 62
  • 1
    It's possible that while you are setting the width to 80% inside of your estimatedFrame function that you are not factoring in the padding within the UITextView and that the slight difference in available width in the textview vs the 80% calculation is resulting in a different height being calculated. – Steve Feb 25 '18 at 16:51
  • @Steve thanks steve, you have a really sharp eye. Basically I removed all paddings and edgeInsets, and the results are much better. Thereafter add some random constants to do minor adjustments. – Koh Feb 27 '18 at 12:46
  • Happy to help. Mind if I make that an answer so you can make it the accepted solution? – Steve Feb 27 '18 at 12:47
  • @Steve sure! Thanks so much – Koh Feb 27 '18 at 12:50

1 Answers1

0

It's possible that while you are setting the width to 80% inside of your estimatedFrame function that you are not factoring in the padding within the UITextView and that the slight difference in available width in the textview vs the 80% calculation is resulting in a different height being calculated.

Steve
  • 921
  • 1
  • 7
  • 18