NSUnderlineStyle
Is Not the Proper Type
Precisely, but somewhat unhelpfully, the documentation explains that the underlineStyle
attribute key takes an attribute value of an "NSNumber
containing an integer."
The value of this attribute is an NSNumber object containing an
integer. This value indicates whether the text is underlined and
corresponds to one of the constants described in NSUnderlineStyle. The
default value for this attribute is styleNone.
Many readers are drawn to NSUnderlineStyle
, click on it, and find an enum-like list of styles: single
, thick
, patternDash
, double
, etc. That all seems like a good Swifty solution. The reader intuits that this enum must be the way to specify the attribute value, and types the sort of code shown in the question, above.
The rawValue
Property of NSUnderlineStyle
Provides The Proper Type
NSUnderlineStyle
is not an enum. It is a struct that conforms to the OptionSet
protocol. The OptionSet
protocol is a convenient, Swifty way of setting the bits of an integer, with each bit representing a binary state. The NSUnderlineStyle
struct is a wrapper around that integer. Its enum-like styles actually are static vars on the struct, each of which returns an instance of NSUnderlineStyle
with the embedded integer, with just one of the bits flipped to correspond to the desired style.
NSAttributedString
's dictionary of attributes takes literally anything as a value, so it happily accepts instances of NSUnderlineStyle
. But TextKit, the engine that makes UITextView
and UILabel
work, doesn't know what to do with an NSUnderlineStyle
instance. When TextKit tries to render an underlineStyle
attribute, it wants an integer so that it can determine the correct style by checking the bits of the integer.
Fortunately, there is an easy way to get at that integer. The rawValue
property of NSUnderlineStyle
vends it. Thus, a properly working version of our code snippet is as follows:
let string = NSMutableAttributedString(string: "This is my string.")
string.addAttributes([.underlineStyle : NSUnderlineStyle.single.rawValue],
range: NSRange(location: 5, length: 2))
The only difference between this code and the code in the question is the addition of ".rawValue" to "NSUnderlineStyle.single". And, "is" is underlined.
As a side note, the type vended by the rawValue
property is an Int
, not an NSNumber
. The documentation's reference to the attribute value being an NSNumber
can be confusing. Under the hood, to interoperate with ObjC, NSAttributedString
wraps the Int
inside an NSNumber
. There is no need for (or benefit to?) explicitly wrapping the Int
in NSNumber
.
Use the OptionSet
union
Method to Combine Underline Styles
We can combine multiple underline styles together into a compound style. At the bit-wise level, this is accomplished by taking the logical OR of the integers representing two styles. Being an OptionSet
, NSUnderlineStyle
provides a Swifty alternative. The union(_:)
method on NSUnderlineStyle
effectuates the logical OR in a more plain-English fashion. As an example:
NSUnderlineStyle.double.union(.patternDot).rawValue
This code produces an Int
with the appropriate bits flipped, and TextKit draws a very pretty dotted double-underline. Of course, not all combinations work. 'NSAttributedString' might accept anything, but common sense and the TextKit
engine ultimately will dictate.
NSRange
. The range of "is" should almost always be calculated. (In this simple example, it obviously doesn't matter, but in most cases.) – Pinta