Attributed label won't go away when tableviewcell is reused in Swift/Objective-C

Viewed 393

For tableviews, I have never had an issue with a text label persisting after a cell is reused.

When you reload a tableview after the datasource has changed, the tableviewcells get the latest data and draw themselves accordingly.

However, I am now having this problem with an attributed label. After removing an item from the datasource so that the tableviewcell gets re-used the attributed label persists in the cell. Needless to say, the leftover attributed label has nothing to do with what should be in the cell. It's just attached to the cell and won't go away.

The problem is described here:

In the above answer, the person said he got rid of the attributed label by setting it to nil prior to doing anything with the regular text label.

transactionDescriptionLabel.attributedText = nil
transactionValueLabel.attributedText = nil
Or, if I reset the attributedText first, and then reset the text, it also works:

transactionDescriptionLabel.attributedText = nil
transactionValueLabel.attributedText = nil
transactionDescriptionLabel.text = nil
transactionValueLabel.text = nil 

Setting the attributed label to nil seems to solve the problem for me but I have never had to set a regular textLabel to nil before and am trying to get my arms around why I would have to set the attribute label to nil.

When you have an attribute label, is it necessary to set it to nil when you dequeue the cell? Or, apart from setting it to an empty string, what is the proper way to get it to disappear when you are no longer displaying it in the tableview?

Thanks in advance for any suggestions.

2 Answers

I just stumbled upon the exact same issue with NSUnderlineStyleAttributeName. I have to underline some text in a UILabel inside a UITableViewCell only if a condition is met. After scrolling through the cell, the label keeps reusing the attributes.

I tried several workaround, by setting label.text and label.attributedText to nil, removing attributes etc... nothing worked.

Finally, I found the actual solution looking at NSAttributedString documentation (RTFM, is always the answer):

There are enums defined for underline, and strikethrough attributes :

NSUnderlineStyleNone = 0x00

UIKIT_EXTERN NSAttributedStringKey const NSStrikethroughStyleAttributeName API_AVAILABLE(macos(10.0), ios(6.0));  // NSNumber containing integer, default 0: no strikethrough
UIKIT_EXTERN NSAttributedStringKey const NSUnderlineStyleAttributeName API_AVAILABLE(macos(10.0), ios(6.0));      // NSNumber containing integer, default 0: no underline

In conclusion, the actual way to remove underline or strikeThrough attribute is to set it to NSUnderlineStyleNone or 0.

NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:text];
[attributeString setAttributes:@{NSUnderlineStyleAttributeName:@(NSUnderlineStyleNone)} range:(NSRange){0, [attributeString length]}];
label.attributedText = attributeString;

label.attributedText will have styling properties of the string as well.

Before reusing, I mean before setting the attributedText property even once, the styling property would be set to default. If you use a xib/storyboard, you can see the default style for the label in attribute inspector or if you have edited these values then you know it already.

Now, if you set label.text = "This is just text". This takes the default styling provided.

And at some point if you have to assign

  1. label.attributedText = NSAttributedString(string: "Attributed text", attributes: someAttributes)

So from now, if you reuse this label, and set just the text like
label.text = "This is just text"
This causes the label to reuse the styling provided in previous cases via someAttributes

So if there's a case where you reuse your cell and the label inside it has different styles in different rows, better always set the attributedText property with you required attributes. Like point 1

Now, if you don't need the label entirely you can remove it from the view heirarchy by label.removeFromSuperview()(handle reuse carefully in this case, use this only when you are sure that you won't be reusing this view accessing the removed label)

Related