zenangst / Spots

:bamboo: Spots is a cross-platform view controller framework for building component-based UIs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Item with uilabel not expanding with text after sizeToFit

bmctigue opened this issue · comments

Good morning,
I have a very simple tableview cell that includes a label. In the configuration method for the Item I set the text on the label and then call sizeToFit() on the label. At that point I use the label's width and height in the compute size method. The height is wrong, because the sizeToFit is called before the label's width has been calculated. This cell works perfectly in a normal table, but it seems there's timing issue when configure is called. The layout of the cell hasn't happened. Do you have any examples of how to do this with the latest version. The examples are old as you know. Thanks in advance.

Hey @bmctigue, mind sharing a code example so that we can take a closer look at the issue? :)

Good morning! Thanks for replying. Here's a cell with just a couple of labels. Trying to resize the responseLabel:

import UIKit
import Spots

class AssistantTextOutputCell: BaseAsstTableViewCell, ItemConfigurable {

    @IBOutlet weak var mainView: AstroUIView!
    @IBOutlet weak var responseLabel: UILabel!
    @IBOutlet weak var timestampLabel: UILabel!
    @IBOutlet weak var bottomConstraint: NSLayoutConstraint!
    
    var testLabel: UILabel!
    
    var index: Int!
    
    func update() {
        if index < result.responses.count {
            responseLabel.text = result.responses[index]
        } else {
            responseLabel.text = nil
        }
        responseLabel.sizeToFit()
        
        //            log.debug("index \(self.index) result response count \(self.result.responses.count)")
        if index == result.responses.count - 1 {
            timestampLabel.text = TimestampUtils().getConversationTimestamp(date: result.date)
            bottomConstraint.constant = 12
        } else {
            bottomConstraint.constant = 2
        }
    }
    
    func configure(with item: Item) {
//        if index == result.responses.count - 1 {
//            timestampLabel.text = item.meta["timeStamp"] as? String
//            bottomConstraint.constant = 12
//        } else {
//            bottomConstraint.constant = 2
//        }
        bottomConstraint.constant = 2
        
//        if index < result.responses.count {
//            responseLabel.text = item.meta["responseText"] as? String
//        } else {
//            responseLabel.text = nil
//        }
        
        responseLabel.frame = CGRect(x: responseLabel.frame.origin.x, y: responseLabel.frame.origin.y, width: UIScreen.screens[0].bounds.width - 90.0, height: 300)
        responseLabel.text = item.meta["responseText"] as? String
        responseLabel.sizeToFit()
    }
    
    func computeSize(for item: Item) -> CGSize {
        return CGSize(width: self.contentView.frame.size.width, height: self.contentView.frame.size.height)
    }
}

Thanks!

Feels like the content view has yet to receive its new size when you return the size for the item in the computeSize(for item: Item) -> CGSize method. If the view is dependent on the size of the response label you could potentially return that as the height argument in your return statement.

func computeSize(for item: Item) -> CGSize {
  return CGSize(width: self.contentView.frame.size.width, height: responseLabel.frame.maxY)
}

Ok, here's a simplified version to show the issue better. So this configure code matches the code the regular tableview uses in the update method. The size is taken from the responseLabel. Following is a screenshot showing the issue. The height was calculated on the label before it has been adjusted with sizeToFit, so the height is way too high. The width is evaluated after the size is called. So the width is correct, but the height is wrong.

import UIKit
import Spots

class AssistantTextOutputCell: BaseAsstTableViewCell, ItemConfigurable {

    @IBOutlet weak var mainView: AstroUIView!
    @IBOutlet weak var responseLabel: UILabel!
    @IBOutlet weak var timestampLabel: UILabel!
    @IBOutlet weak var bottomConstraint: NSLayoutConstraint!
    
    var testLabel: UILabel!
    
    var index: Int!
    
    func update() {
        if index < result.responses.count {
            responseLabel.text = result.responses[index]
        } else {
            responseLabel.text = nil
        }
        responseLabel.sizeToFit()
        
        //            log.debug("index \(self.index) result response count \(self.result.responses.count)")
        if index == result.responses.count - 1 {
            timestampLabel.text = TimestampUtils().getConversationTimestamp(date: result.date)
            bottomConstraint.constant = 12
        } else {
            bottomConstraint.constant = 2
        }
    }
    
    func configure(with item: Item) {
        responseLabel.text = item.meta["responseText"] as? String
        responseLabel.backgroundColor = UIColor.orange
        responseLabel.sizeToFit()
    }
    
    func computeSize(for item: Item) -> CGSize {
        return CGSize(width: self.responseLabel.frame.size.width, height: self.responseLabel.frame.size.height)
    }
}

What size does computeSize return if you set a breakpoint and print the return statement?

Here you go.

Printing description of self.responseLabel:
▿ <UILabel: 0x7febcfd35bb0; frame = (8 8; 40 36); text = 'SDIOFDIFj asidfa spofiuas...'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000002896f0>>
Printing description of self.responseLabel:
▿ <UILabel: 0x7febcfd35bb0; frame = (8 8; 40 363); text = 'SDIOFDIFj asidfa spofiuas...'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000002896f0>>

The first size is before sizeToFit is called. You can see the width is forty, which is the width the label has in its xib file. The second size is the size from the computeSize call. The height is adjusted there after the sizeToFit(), but it was calculated with the 40 width. You can see the width is still 40 here.

Do you use constraints for the responseLabel? Perhaps you need to invoke needsUpdateConstraints() or layoutIfNeeded.

I've tried this, but here you go. The width is now correct, but the height is now too short:

class AssistantTextOutputCell: BaseAsstTableViewCell, ItemConfigurable {

    @IBOutlet weak var mainView: AstroUIView!
    @IBOutlet weak var responseLabel: UILabel!
    @IBOutlet weak var timestampLabel: UILabel!
    @IBOutlet weak var bottomConstraint: NSLayoutConstraint!
    
    var testLabel: UILabel!
    
    var index: Int!
    
    func update() {
        if index < result.responses.count {
            responseLabel.text = result.responses[index]
        } else {
            responseLabel.text = nil
        }
        responseLabel.sizeToFit()
        
        //            log.debug("index \(self.index) result response count \(self.result.responses.count)")
        if index == result.responses.count - 1 {
            timestampLabel.text = TimestampUtils().getConversationTimestamp(date: result.date)
            bottomConstraint.constant = 12
        } else {
            bottomConstraint.constant = 2
        }
    }
    
    func configure(with item: Item) {
        responseLabel.text = item.meta["responseText"] as? String
        responseLabel.backgroundColor = UIColor.orange
        self.contentView.layoutIfNeeded()
        responseLabel.sizeToFit()
    }
    
    func computeSize(for item: Item) -> CGSize {
        return CGSize(width: self.responseLabel.frame.size.width, height: self.responseLabel.frame.size.height)
    }
}

You probably have to take into account the font size to get the calculation just right. Try adding the font size as an offset in addition to the responseLabel height.

Hi Christoffer, I really appreciate the help. I didn't really intend to try to troubleshoot this with someone. I expect the Item to display just as a normal tableview or collection view does. If it really doesn't and you don't consider this an issue with the library, then I'll have to find another solution. A workaround isn't really a long term solution for me. I am surprised that no one but me has seen this issue. Any, thanks again for the help. We can close this issue.

It should work in a similar fashion as UITableView or UICollectionView, mainly because the underlaying implementation is either UITableView or UICollectionView depending on your ComponentModel.kind. Just trying to figure out if this is a bug in the framework or if your constraints are conflicting in any way. I can probably setup a small demo that looks like this next week to see if I can reproduce it :) I'm gonna keep this issue open and keep you posted.

I've seen that we can't really accommodated the desired behavior of using self sizing cells that use auto layout as it will conflict with the internal size caching that the framework uses. So you will have to calculate and provide the size yourself if you want something to scale based of data input.