The following code, will create a red color text, without strike-through.
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let text = "Hello World"
let textCount = text.count
let fullRange = NSRange(location: 0, length: textCount)
var attributedText = NSMutableAttributedString(string: text)
attributedText.addAttribute(.foregroundColor, value: UIColor.green, range: fullRange)
attributedText.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: fullRange)
label.attributedText = attributedText
attributedText = NSMutableAttributedString(string: text)
attributedText.addAttribute(.foregroundColor, value: UIColor.red, range: fullRange)
attributedText.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: fullRange)
label.attributedText = attributedText
}
}
However, if I trigger label.text
in between, it will cause the following strange behavior : A red color text, with strike-through created at the end of function.
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let text = "Hello World"
let textCount = text.count
let fullRange = NSRange(location: 0, length: textCount)
var attributedText = NSMutableAttributedString(string: text)
attributedText.addAttribute(.foregroundColor, value: UIColor.green, range: fullRange)
attributedText.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: fullRange)
label.attributedText = attributedText
// Why this will cause a red color text, with strike-through created at the end of function?
label.text = text
attributedText = NSMutableAttributedString(string: text)
attributedText.addAttribute(.foregroundColor, value: UIColor.red, range: fullRange)
attributedText.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: fullRange)
label.attributedText = attributedText
}
}
Does anyone what is the reason behind this behavior, and how I can avoid such? Thank you.
Based on some searching and quick testing, this appears to be a long-standing issue. Bug? Quirk? Who knows...
First note -- when your code does this:
// 1
attributedText = NSMutableAttributedString(string: text)
// 2
attributedText.addAttribute(.foregroundColor, value: UIColor.red, range: fullRange)
// 3
attributedText.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: fullRange)
// 4
label.attributedText = attributedText
the // 3
line doesn't do anything ... you have not added the strikethrough attribute, so it's not there to remove.
The only way I've seen to remove the strike through is to set it in your new attributed string with a value of 0
(zero):
attributedText = NSMutableAttributedString(string: text)
attributedText.addAttribute(.foregroundColor, value: UIColor.red, range: fullRange)
// set strikeThrough to Zero
attributedText.addAttribute(.strikethroughStyle, value: 0, range: fullRange)
label.attributedText = attributedText
There is a lot that goes on with .attributedText
and .text
... if you want to understand why this is happening, let me know and I'll add some details.
Edit - some additional info...
Note: I don't work for Apple, and I don't have the source code for UILabel
-- this is based solely on observation.
Let's start by defining a couple elements:
let textStr: String = "Hello World"
var labelFont: UIFont = {
guard let f = UIFont(name: "TimesNewRomanPS-ItalicMT", size: 40.0)
else { fatalError("Could not create Label Font!!!") }
return f
}()
var attribFont: UIFont = {
guard let f = UIFont(name: "ChalkboardSE-Regular", size: 48.0)
else { fatalError("Could not create Label Font!!!") }
return f
}()
let labelColor: UIColor = .systemRed
let attribColor: UIColor = .systemGreen
// so we can see the label framing
testLabel.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
We'll also set some "typical" properties of the label:
// set label font and text color
testLabel.font = labelFont
testLabel.textColor = labelColor
testLabel.textAlignment = .center
If we set attributed text with an NSAttributedString
with no additional attributes, the attributed text will "inherit" the label's properties:
// attributed text with no attributes
let attributedText = NSMutableAttributedString(string: textStr)
testLabel.attributedText = attributedText
output:
If we use a Font attribute, but not color:
let textCount = textStr.count
let fullRange = NSRange(location: 0, length: textCount)
// attributed text with no attributes
let attributedText = NSMutableAttributedString(string: textStr)
// attributed text with ONLY font
attributedText.addAttribute(.font, value: attribFont, range: fullRange)
testLabel.attributedText = attributedText
output:
As we see, the color is inherited.
If we use font and color attributes:
let textCount = textStr.count
let fullRange = NSRange(location: 0, length: textCount)
// attributed text with font and color
attributedText.addAttribute(.foregroundColor, value: attribColor, range: fullRange)
attributedText.addAttribute(.font, value: attribFont, range: fullRange)
testLabel.attributedText = attributedText
we get this (as expected):
Note that if we again set attributed text with no additional attributes, we get the label's properties again:
let textCount = textStr.count
let fullRange = NSRange(location: 0, length: textCount)
// attributed text with font and color
attributedText.addAttribute(.foregroundColor, value: attribColor, range: fullRange)
attributedText.addAttribute(.font, value: attribFont, range: fullRange)
testLabel.attributedText = attributedText
let newStr = "Goodbye"
let newAttributedText = NSMutableAttributedString(string: newStr)
testLabel.attributedText = newAttributedText
output:
Now, what if we set the .text
property of a label that already has an .attributedText
property... (and the attributes span the full length of the text)?
In that case, the label inherits the attributes from the attributedText
:
let textCount = textStr.count
let fullRange = NSRange(location: 0, length: textCount)
let attributedText = NSMutableAttributedString(string: textStr)
// attributed text with font and color
attributedText.addAttribute(.foregroundColor, value: attribColor, range: fullRange)
attributedText.addAttribute(.font, value: attribFont, range: fullRange)
testLabel.attributedText = attributedText
// set the .text property
testLabel.text = textStr
// the label has now inherited the font and color from the attributedText
let newStr = "Goodbye"
// no additional attributes
let newAttributedText = NSMutableAttributedString(string: newStr)
testLabel.attributedText = newAttributedText
output:
So... what's happening with your .strikethroughStyle
issue?
let textCount = textStr.count
let fullRange = NSRange(location: 0, length: textCount)
let attributedText = NSMutableAttributedString(string: textStr)
// attributed text with font, color AND strike-through
attributedText.addAttribute(.foregroundColor, value: attribColor, range: fullRange)
attributedText.addAttribute(.font, value: attribFont, range: fullRange)
attributedText.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: fullRange)
testLabel.attributedText = attributedText
output:
A UIFont
does not have a strike-through style -- at least, not exposed to us.
So, if we set attributed text with a strike-through and then set the .text
property:
let textCount = textStr.count
let fullRange = NSRange(location: 0, length: textCount)
// attributed text with no attributes
let attributedText = NSMutableAttributedString(string: textStr)
// attributed text with font, color AND strike-through
attributedText.addAttribute(.foregroundColor, value: attribColor, range: fullRange)
attributedText.addAttribute(.font, value: attribFont, range: fullRange)
attributedText.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: fullRange)
testLabel.attributedText = attributedText
// set the .text property
// the label inherits the attributes
testLabel.text = ""
the label has now inherited a Font with strike-through...
and if we follow that with a Font only attributed string:
let newStr = "Goodbye"
let newFullRange = NSRange(location: 0, length: newStr.count)
let newAttributedText = NSMutableAttributedString(string: newStr)
// set ONLY the font
let newFont: UIFont = .italicSystemFont(ofSize: 40.0)
newAttributedText.addAttribute(.font, value: newFont, range: newFullRange)
testLabel.attributedText = newAttributedText
output:
we get the new font, and the label applies its properties/attributes - color and strike-through.
To get rid of the strike-through, we need to set the attributed text with .strikethroughStyle
set to Zero:
newAttributedText.addAttribute(.strikethroughStyle, value: 0, range: newFullRange)
output:
Keep in mind -- if we follow that with a new attributed string that does not address strike-through:
let attributedTextB = NSMutableAttributedString(string: "What now?")
testLabel.attributedText = attributedTextB
the label still has the strike-through (and previously set font) attributes:
As a side-note ... if the attributes are not applied to the full string, the label will not inherit those attributes when setting the .text
property.
Bottom line:
Avoid using the .text
property when working with attributed strings.
If you must do so, make sure you are "re-setting" attributes.
Think about table view cells. A common beginner question related to cellForRowAt
:
if someCondition {
cell.myLabel.textColor = .red
}
Why do the labels in all my cells turn red, even when someCondition
is false?
And the answer is -- cells are reused, and the color needs to be "reset":
if someCondition {
cell.myLabel.textColor = .red
} else {
cell.myLabel.textColor = .black
}