Friday, February 3, 2017

UILabel with custom UIEdgeInsets truncating in UITableViewCell

Leave a Comment

I have an UITableView with cells that contains an UILabel. The UILabel have a custom UIEdgeInset. I subclassed the UILabel and set the UIEdgeInsets like this:

override func drawText(in rect: CGRect) {     super.drawText(in: UIEdgeInsetsInsetRect(rect, insets)) }  override var intrinsicContentSize: CGSize {     var contentSize = super.intrinsicContentSize     contentSize.width += leftInset + rightInset     contentSize.height += topInset + bottomInset     return contentSize } 

But the label gets truncated sometimes when I have more lines in the UILabel. I've already configured the row height to UITableViewAutomaticDimension and set the estimatedRowHeight. Also the constraints are fine. The problem seems to be when I'm setting the UIEdgeInsets since it works fine if I don't customize it.

Probably I should tell the cell to update the constraints after setting the insets, but I couldn't do this so far.

The constraints where added in storyboard. Bottom, Top, Leading and Trailing are related to the superview (UITableViewCell). All constants set to 0.

In cellForRowAtIndexPath the code is as follows:

let cell = tableView.dequeueReusableCell(withIdentifier: "AnswersCell", for: indexPath) as! AnswerCell   cell.answerLabel.text = alternatives[indexPath.row] return cell 

3 Answers

Answers 1

There are two issues that I can see:

  1. The width of the label's intrinsicContentSize is increased by your left and right content insets. The intrinsic width of the label's content should either stay the same or be decreased, depending on whether you regard the intrinsic size as including padding or not.

  2. During auto layout of a multiline label, UILabel does not compute its height using the width provided by its intrinsicContentSize property, but instead uses preferredMaxLayoutWidth, which has not been adjusted to take account of your content insets.

Suggested solutions below.

1. Correct the size returned by intrinsicContentSize

In your current implementation, the rect passed to super.drawText(in:) is the default rect inset by your content insets (i.e. the default rect less padding), but the value of intrinsicContentSize is the default size increased by your content insets (i.e. the default size plus padding). In other words, the size of the rect in which the text is drawn is not the same as the intrinsic content size.

Specifically, the width of the rect in which the text is drawn does not include padding, whereas the width of the intrinsic content size not only includes padding but adds that padding to the width of the table view cell (being the width returned by super.intrinsicContentSize).

I assume your intention was for the intrinsic content size of your label subclass to be the size of the content (text) plus any content inset (padding). If so, delete this line:

contentSize.width += leftInset + rightInset 

The effect is that the intrinsic width of the label will be determined by its auto layout constraints and hence by the width of the table view cell. The intrinsic width of the label will include padding in the same way that the intrinsic height includes padding.

2. Set an appropriate value for preferredMaxLayoutWidth

The above is not sufficient to achieve your goal, because in performing auto layout for a multiline label, UILabel does not use its intrinsic width to compute its height, but instead uses the value of preferredMaxLayoutWidth. So correcting the width of the label's intrinsic content size is not enough to correct the height computed by auto layout.

The header file for UILabel provides the following comment for preferredMaxLayoutWidth:

// If nonzero, this is used when determining -intrinsicContentSize for multiline labels

In your case, the desired value for preferredMaxLayoutWidth is the auto layout width of the label (i.e. the width of the table view cell) less any left and right content insets. The most practical way to set that property is in your table view cell subclass, because that is the most straightforward place to access the width of the table view cell during auto layout.

When performing auto layout for self-sizing table view cells, UITableView calls systemLayoutSizeFitting(_:withHorizontalFittingPriority:verticalFittingPriority) on each cell. (See WWDC 2014 Session 226.) The fitting size passed to that method is the width of the table view with a zero height, and the horizontal fitting priority is UILayoutPriorityRequired.

So, override that method and set the desired value for preferredMaxLayoutWidth:

//  AnswerCell.swift  @IBOutlet weak var answerLabel: AnswerLabel!  override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {     answerLabel.preferredMaxLayoutWidth = targetSize.width - answerLabel.insets.left - answerLabel.insets.right     return super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority) } 

The effect is that, in computing its height during auto layout, UILabel uses a value of preferredMaxLayoutWidth that has been adjusted for your content insets, which ensures that UITableView receives the correct heights for its cells.

Addendum

Alternatively, instead of all of the above, you could simply change the constraints on your label to inset the whole label, either directly in the content view of the table view cell or by embedding it in another subview of the content view.

Answers 2

Set these two properties of UILable from Attributes inspector section of storyboard

  1. Lines to 0
  2. Line break to word wrap

or you can also set these properties from code.

self.answerLabel.numberOfLines = 0 self.answerLabel.lineBreakMode = .byWordWrapping 

and put below code in your UILable's subclass

class CustomLabel: UILabel {  @IBInspectable var topInset: CGFloat = 5.0 @IBInspectable var bottomInset: CGFloat = 5.0 @IBInspectable var leftInset: CGFloat = 20.0 @IBInspectable var rightInset: CGFloat = 5.0  override func drawText(in rect: CGRect) {     let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)     super.drawText(in: UIEdgeInsetsInsetRect(rect, insets)) }  override var intrinsicContentSize: CGSize {     get {         var contentSize = super.intrinsicContentSize         contentSize.height += topInset + bottomInset         contentSize.width += leftInset + rightInset         return contentSize     } } 

}

try this.

Hope it will work for you..

Answers 3

I subclassed my Label like this:

class PaddingLabel: UILabel {      var topInset: CGFloat = 0     var bottomInset: CGFloat = 0     var leftInset: CGFloat = 0     var rightInset: CGFloat = 0      override func drawText(in rect: CGRect) {         let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)         super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))     }      override var intrinsicContentSize: CGSize {         get {             var contentSize = super.intrinsicContentSize             contentSize.height += topInset + bottomInset             contentSize.width += leftInset + rightInset             return contentSize         }     } } 

Maybe it's the get that's missing.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment