0

I have a UICollectionView which contains five cells. In there, I have a UIImageView, two UILabels and a UITextView. I want to change the height of the textview based on the text it contains, so afterwards I can set the height of the entire cell based on the height of the UITextView and the labels above them. Let me demonstrate with a screenshot.

So, as you can tell, the red background shows the height of the UITextView is not right. I set up the UITextView like this:

    let commentTextView: UITextView = {

    let textView = UITextView()
    textView.translatesAutoresizingMaskIntoConstraints = false
    textView.text = "This is just some text to act as a description"
    textView.textContainerInset = UIEdgeInsets(top: 0, left: -4, bottom: 0, right: 0)
    textView.font = UIFont.systemFont(ofSize: 16.0)
    textView.textColor = UIColor.darkGray
    textView.isEditable = false
    textView.isSelectable = false
    textView.backgroundColor = UIColor.red

    textView.frame.size.width = (UIScreen.main.bounds.width - 16 - 16 - 60 - 8)

    let numberOfLines = (textView.contentSize.height / textView.font!.lineHeight)
    var textViewHeight = (textView.font?.lineHeight)! * floor(numberOfLines)

    textView.frame.size.height = textViewHeight

    return textView

}()

This does not create the wrong height, I think. I think the problem can be found in my constraints, which has a height constraint (if I delete it, the UITextView disappears). If I change the multiplier (currently set at 0.3), I have different heights, but I want this to be dynamically. So in my opinion, I would need to set a dynamic variable inside the multiplier, but I have no idea how to compose it. Could anyone help? Here are my constraints:

        // top constraints
    addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .top, relatedBy: .equal, toItem: workoutLabel, attribute: .bottom, multiplier: 1, constant: 2)])

    // left constraint
    addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .left, relatedBy: .equal, toItem: profilePictureImageView, attribute: .right, multiplier: 1, constant: 16)])

    // right constraint
    addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .right, relatedBy: .equal, toItem: self.commentTextView.superview, attribute: .right, multiplier: 1, constant: -16)])

    // height constraint
    addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 0.3, constant: 1)])

Cheers guys!

PennyWise
  • 543
  • 6
  • 23

3 Answers3

0

You could consider another way about dynamic height of UITextView. It's known about intrinsic content size. You could implement dynamic height with that tech.

AechoLiu
  • 15,710
  • 9
  • 85
  • 113
  • I’ll take a look at this first thing tomorrow morning. Is this the better/best practice way to achieve this? This is at all possible with a TextView inside a cell (because in the example you refer to, they call it in viewDidLoad) – PennyWise Sep 14 '18 at 01:41
  • 1
    Let `intrinsic content size` decide the height (or width) of `UITextView` or `UILabel`. It works well with `AutoLayout`. In `UILabel`, when it's text changed, then it's intrinsic content size will change too. – AechoLiu Sep 14 '18 at 02:05
  • 1
    That post makes constraints about `top`, `left`, and `right`. But it doesn't constraint about `height`. It's height is decided dynamically with `intrinsic content size`, which is based on the `text` length. – AechoLiu Sep 14 '18 at 02:07
0

Originally answered here.

Don't change or give any frame to your UITextView. Just give it leading, trailing, top & bottom constraints. Then if your cell is capable of having automatic size then you don't need to calculate anything for your text view.

When using Auto-Layout for dynamically sizing cells, you don't really need to implement sizeThatFits(...). If the constraints are setup correctly, then you only need to disable the scrolling of the UITextView.

From code:

yourTextView.scrollEnabled = false

From IB:

Select your Text View and open Attributes inspector, then

enter image description here


Now if you are facing problems with making your cell to have dynamic size then please look at this answer.

nayem
  • 5,897
  • 1
  • 24
  • 49
  • My apologies for the late reply. I have figured out how to use this solution and now the textview indeed adapts to the text inside it (I can see this by setting a background color). However, as you suggested, I am now facing difficulty as to how to dynamically size the Collection View Cell. The post you refer to is about TableView, but I can't seem to figure out how to use this (is rowHeight, estimatedRowHeight only accessible in Table Views?) Any chance you could help me out on this with a Collection View Cell? – PennyWise Sep 15 '18 at 22:21
  • Well, dynamic sizing of collection view cell is out of the scope of this question. You should look out for that or ask a separate question according to your needs altogether. I've also seen you have posted an answer yourself. But the basic idea for `UITableViewCell` & `UICollectionViewCell` to have dynamic size should be same that is ***an unbroken set of constraints that expands from left to right and top to bottom***. – nayem Sep 17 '18 at 02:33
  • Therefore, collection view cell requires some extra effort. I recommend you read [this post](https://stackoverflow.com/questions/25895311/uicollectionview-self-sizing-cells-with-auto-layout) carefully and figure it out. Pay attention to each one of the 1st, 2nd & 3rd answers there. It is a tedious work, but the final result would be worth the effort I hope. – nayem Sep 17 '18 at 02:34
  • It seems to me that I am very ambitious to try and implement this as I am pretty new to Swift & Xcode and this issue is somewhat more complex. That being said, your references are very useful and I will spend today trying to figure out how to implement it correctly. I'll start with a new project and try everything in there, then as soon as I have it working I can continue onto my current project. Thank you again for the references - hope I can manage to get a hang on it. – PennyWise Sep 17 '18 at 08:02
  • I am now trying to set layout.estimatedItemSize (just to 1, 1 - this doesn't really matter I guess) and then I set the topAnchor, leftAnchor, rightAnchor and bottomAnchor constraints on my collectionView to self.view.topAnchor etc. I also have constraints for my label (e.g. "V:|-16-[v0]-2-[v1]-16-|") so it spans over the entire width. It seems to be dynamic, however, when I run the app, it spans over the "needed" width, so I have three cells next to each other now. Can I set the width to the screen width? I tried to do it in the sizeForItemAt method, but this didn't work at all. – PennyWise Sep 17 '18 at 14:47
  • Well seems you're close to what you wanna achieve. Therefore I recommend you ask a separate question with your current progress and the problems you are facing right now. You will get help from others who faced similar issues. This question isn't the right place to discuss about the further issues you are pointing. – nayem Sep 18 '18 at 02:29
  • I agree, my apologies for the confusion. Posted this new question [here](https://stackoverflow.com/questions/52383858/unable-to-set-width-of-uicollectionviewcell-with-autolayout). – PennyWise Sep 18 '18 at 13:14
0

As mentioned yesterday, I had the textview adapt its size based on the answer of @nayem - thank you!

However, I faced another problem with making the cell height dynamic. I have looked into different solutions, got nothing to work, except when I calculate the height myself and set this as the height to be used. This works on all devices in Simulator, just wondering if this is the right approach. Code below, constraints are set on top, left and right.

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    let dummyText = "Just a long text to see what will happen to the cell. Will it adapt or not? Let's find out!"

    let rectWidth = view.frame.width - 32 - 60 - 16

    let rect = NSString(string: dummyText).boundingRect(with:CGSize(width: rectWidth, height: 1000), options: NSStringDrawingOptions.usesFontLeading.union(NSStringDrawingOptions.usesLineFragmentOrigin), attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16)], context: nil)

    let variableHeight = 16 + 20 + 2 + 20 + 2 + 16 + rect.height

    return CGSize(width: view.frame.width, height: variableHeight)

}

Is this the best way to do this? Will this cause problems later on?

PennyWise
  • 543
  • 6
  • 23