Joseph E. Joseph E. - 4 months ago 38
iOS Question

Checking the value of an NSUnderlineStyle BitMask

I would like to be able to correctly read the values of an NSUnderlineStyle in Swift. It's more complicated than it seems.

First, a quick review. NSUnderlineStyle is an enumeration with the following values:

NSUnderlineStyleNone = 0x00,
NSUnderlineStyleSingle = 0x01,
NSUnderlineStyleThick = 0x02,
NSUnderlineStyleDouble = 0x09,
NSUnderlinePatternSolid = 0x0000,

NSUnderlinePatternDot = 0x0100,
NSUnderlinePatternDash = 0x0200,
NSUnderlinePatternDashDot = 0x0300,
NSUnderlinePatternDashDotDot = 0x0400,

NSUnderlineByWord = 0x8000


There are three distinct groups of options. According to Apple's documentation, the "style, pattern, and optionally by-word mask are OR'd together to produce the value for
NSUnderlineStyleAttributeName
and
NSStrikethroughStyleAttributeName
."

NSUnderlineStyle does not implement the new OptionSet Protocol, meaning that values have to be combined with integer bitwise operations (obviously). If it did, something similar to the following would be possible:

let operation: NSDragOperation = [.copy, .move]
if operation.contains(.copy) {
// Yay!
}


But, sadly, that doesn't work.

My understanding is that to correctly check for bit flags with numbers, it's (A & B == B) if B is in A.

However, this does not seem to work for NSUnderlineStyle, and I'm not sure why. I'm not a bitmask expert.

Here's example code demonstrating the problem.

let style = NSUnderlineStyle.styleSingle.rawValue |
NSUnderlineStyle.patternDashDotDot.rawValue |
NSUnderlineStyle.byWord.rawValue

if style & NSUnderlineStyle.styleSingle.rawValue == NSUnderlineStyle.styleSingle.rawValue {
NSLog("style single found")
}

if style & NSUnderlineStyle.styleDouble.rawValue == NSUnderlineStyle.styleDouble.rawValue {
NSLog("style double found")
}

if style & NSUnderlineStyle.styleThick.rawValue == NSUnderlineStyle.styleThick.rawValue {
NSLog("style thick found")
}

if style & NSUnderlineStyle.patternSolid.rawValue == NSUnderlineStyle.patternSolid.rawValue {
NSLog("pattern solid found")
}

if style & NSUnderlineStyle.patternDash.rawValue == NSUnderlineStyle.patternDash.rawValue {
NSLog("pattern dash found")
}

if style & NSUnderlineStyle.patternDot.rawValue == NSUnderlineStyle.patternDot.rawValue {
NSLog("pattern dot found")
}

if style & NSUnderlineStyle.patternDashDot.rawValue == NSUnderlineStyle.patternDashDot.rawValue {
NSLog("pattern dash dot found")
}

if style & NSUnderlineStyle.patternDashDotDot.rawValue == NSUnderlineStyle.patternDashDotDot.rawValue {
NSLog("pattern dash dot dot found")
}

if style & NSUnderlineStyle.byWord.rawValue == NSUnderlineStyle.byWord.rawValue {
NSLog("by word flag found")
}


This code should produce:

style single found
pattern dash dot dot found
by word flag found


But instead produces:

style single found
pattern solid found
pattern dash dot dot found
by word flag found


There are other cases where this fails.

What can I do to read underline styles appropriately?

Any help would be much appreciated. Also, if anyone has any insight as to why this is failing, that would be terrific, too.

Answer

The enumeration values for "style" and "pattern" are not bit flags. For example, NSUnderlineStyleNone and NSUnderlinePatternSolid both have the value zero, and NSUnderlineStyleSingle and NSUnderlineStyleDouble have a bit (0x01) in common.

You have to extract the bits corresponding to each group (style, pattern, flags) and compare that value against the possible enumeration values, separately for each group.

Unfortunately Apple does not provide bit masks for each group (as far as I can see) so we to define our own. The style values (0x00, 0x01, 0x02, 0x09) all use the bits 0...3, and the pattern values (0x0, 0x100, 0x200, 0x300, 0x400) all use the bits 8...11. That suggests the following definitions:

let underlineStyleMask   = 0x000F
let underlinePatternMask = 0x0F00

which then can be used as

// Check style:
switch style & underlineStyleMask {
case NSUnderlineStyle.styleNone.rawValue:
    NSLog("no style")
case NSUnderlineStyle.styleSingle.rawValue:
    NSLog("single style")
case NSUnderlineStyle.styleDouble.rawValue:
    NSLog("double style")
case NSUnderlineStyle.styleThick.rawValue:
    NSLog("thick style")
default:
    NSLog("unknown style")
}

// Check pattern:
switch style & underlinePatternMask {
case NSUnderlineStyle.patternSolid.rawValue:
    NSLog("solid pattern")
case NSUnderlineStyle.patternDot.rawValue:
    NSLog("dot pattern")
case NSUnderlineStyle.patternDash.rawValue:
    NSLog("dash pattern")
case NSUnderlineStyle.patternDashDot.rawValue:
    NSLog("dash dot pattern")
case NSUnderlineStyle.patternDashDotDot.rawValue:
    NSLog("dash dot dot pattern")
default:
    NSLog("unknown pattern")
}

// Check flags:
if style & NSUnderlineStyle.byWord.rawValue != 0 {
    NSLog("by word flag")
}

But note that this might break if Apple adds more definitions in the future, for example

NSUnderlineStyleStrange = 0x22

would wrongly be detected as NSUnderlineStyleThick.