2

I have a table view (row heights are set to automatic), where each cell has a single UIButton (whose top and bottom is constrained to the cell's contentView.top and contentView.bottom, the leading side is fixed to contentView.leading, and the trailing is dynamically set to <= contentView.trailing, dependent upon the length of text). I need the height of the button to grow depending on the amount of text, so that it can show multi-line text if needed.

Now, I have tried various things which seem like they would do the trick. For example, I followed this SO question, which states to set the following:

myButton.titleLabel?.lineBreakMode = .byWordWrapping
myButton.titleLabel?.numberOfLines = 0

This successfully allows the button's titleLabel to become multi-line, but the button is still not adjusting to fit its title label. See this image for reference.

Note: I set the button's content insets, but these values seem to have no effect when the text becomes multi-line. Clipping still occurs

Based on a few other SO questions, I've come to the conclusion that it has something to do with the UIButton's intrinsicContentSize, so this SO question recommended subclassing UIButton and then overriding the intrinsicContentSize property...

class ResizableButton: UIButton {
    
    override var intrinsicContentSize: CGSize {
        return self.titleLabel!.intrinsicContentSize
    }

    // Whever the button is changed or needs to layout subviews,
    override func layoutSubviews() {
        super.layoutSubviews()
        titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
    }
}

...but when I tried this, I got an even wonkier result. It seems to me that I must not be returning the correct value for the intrinsicContentSize property. If this is the case, does anyone know how I can return the correct value to achieve the result I am looking for? Otherwise, if this is not correct, any other suggestions are welcome. I'm also unsure about my current implementation of the layoutSubviews method.

Note: I know I could just create a separate UILabel, and handle the multi-line text separately from the UIButton, but I prefer to keep the text within the button itself if this is possible, especially since UIButton's handle selection/deselection animations inherently.

UPDATE: I have tried @valosip's solution. This fixed the content inset issue, but the text is still starting a new line after only a couple letters instead of taking up the full width (See image here). Any solutions for this? Again, note that I have the trailing constraint of the UIButton to be <= contentView.trailing, but UIButton is clearly not getting anywhere near the trailing edge.

Eric
  • 51
  • 5

1 Answers1

1

You can update your intrinsicContentSize to add the content insets

class ResizableButton: UIButton {

    override var intrinsicContentSize: CGSize {
        guard let title = titleLabel else {
            return super.intrinsicContentSize
        }
        let size = title.intrinsicContentSize
        return CGSize(width: size.width + contentEdgeInsets.left + contentEdgeInsets.right, height: size.height + contentEdgeInsets.top + contentEdgeInsets.bottom)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        guard let title = titleLabel else { return }
        titleLabel?.preferredMaxLayoutWidth = title.frame.size.width
    }
}
valosip
  • 3,167
  • 1
  • 14
  • 26
  • Thank you for your response. It helped with the content inset issue, but I'm still having problems with the text starting a new line after only a couple of characters. If you don't mind, please see my update at the bottom of the post for more info. – Eric Apr 16 '21 at 22:22
  • any suggestions? – Eric Apr 16 '21 at 23:56
  • @Eric Update your question with how you're setting up the buttons (programmatically or storyboard) and show us your constraints. Based on your second (updated) image, your constraints look wrong. – valosip Apr 17 '21 at 01:53