jz_
jz_

Reputation: 358

NSMutableAttributedString: How do I retrieve attributes over a range

I have set up a simple playground to demonstrate my problem in retrieving the attributes of attributed strings. Perhaps I do not understand how to define ranges: perhaps I am missing something else.

I have define a NSMutableAttributedString that has two colors in it as well as a change in font to bold(for the blue) and italic(for the red):

var someText =  NSMutableAttributedString()

let blueAttri : [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 12), NSAttributedString.Key.foregroundColor : UIColor.blue]
let redAttri : [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: UIFont.italicSystemFont(ofSize: 12), NSAttributedString.Key.foregroundColor : UIColor.red]

someText = NSMutableAttributedString(string: "Some blue string here; ", attributes: blueAttri)
someText.append(NSMutableAttributedString(string: "Some red string here; ", attributes: redAttri))

At this point the string looks fine showing text with both colors.

I then have defined 3 ranges (attempting different approaches to defining ranges.)

var range1 = NSRange()
var range2 = NSRange(location: 0, length: someText.length)
var range3 = NSRange(location: 40, length: 44)

Lastly, I try to retrieve attributes for the text

// retrieve attributes
let attributes1 = someText.attributes(at: 0, effectiveRange: &range1)

// iterate each attribute
print("attributes1")
for attr in attributes1 {
    print(attr.key, attr.value)
}

let attributes2 = someText.attributes(at: 0, effectiveRange: &range2)

// iterate each attribute
print("attributes2")
for attr in attributes2 {
    print(attr.key, attr.value)
}


let attributes3 = someText.attributes(at: 0, effectiveRange: &range3)

// iterate each attribute
print("attributes3")
for attr in attributes3 {
    print(attr.key, attr.value)
}

I get the following results. In all cases showing only the first set of attributes.

attributes1
NSAttributedStringKey(_rawValue: NSColor) UIExtendedSRGBColorSpace 0 0 1 1
NSAttributedStringKey(_rawValue: NSFont) font-family: ".SFUIText-Semibold"; font-weight: bold; font-style: normal; font-size: 12.00pt

attributes2
NSAttributedStringKey(_rawValue: NSColor) UIExtendedSRGBColorSpace 0 0 1 1 NSAttributedStringKey(_rawValue: NSFont) font-family: ".SFUIText-Semibold"; font-weight: bold; font-style: normal; font-size: 12.00pt

attributes3
NSAttributedStringKey(_rawValue: NSColor) UIExtendedSRGBColorSpace 0 0 1 1
NSAttributedStringKey(_rawValue: NSFont) font-family: ".SFUIText-Semibold"; font-weight: bold; font-style: normal; font-size: 12.00pt

What do I need to do to get all of the attributes in the string?

It has been suggested I use enumerate attributes. That does not seem to be legal. See below: enter image description here

Upvotes: 4

Views: 7131

Answers (2)

rmaddy
rmaddy

Reputation: 318794

You are using attributes with the same at: value of 0 for all three calls. That returns the attributes for the 1st character in the string.

If you want all of the attributes in a range, use enumerateAttributes and pass in the range you want to iterate over.

Note: The range you pass needs to be based on the UTF-16 encoding of the string, not the Swift string length. Those two lengths can be different when you have special characters such as Emojis. Using NSAttributedString length is fine.

Upvotes: 1

jz_
jz_

Reputation: 358

Using enumerateAttributes I was able to capture ranges with Italic (just to show one example). The following is the complete code:

//Create Empty Dictionaries for storing results
var attributedFontRanges = [String: UIFont]()
var attributedColorRanges = [String: UIColor]()

//Find all attributes in the text.
someText.enumerateAttributes(in: NSRange(location: 0, length: someText.length)) { (attributes, range, stop) in

    attributes.forEach { (key, value) in
        switch key {

        case NSAttributedString.Key.font:
            attributedFontRanges[NSStringFromRange(range)] = value as? UIFont

        case NSAttributedString.Key.foregroundColor:
            attributedColorRanges[NSStringFromRange(range)] = value as? UIColor

        default:

            assert(key == NSAttributedString.Key.paragraphStyle, "Unknown attribute found in the attributed string")
        }
}
}

//Determine key(range) of Italic font
var italicRange = attributedFontRanges.filter { $0.value.fontName == ".SFUIText-Italic" }.keys

print("italicRange: \(italicRange)")

The following results are printed: italicRange: ["{23, 22}"]

Upvotes: 8

Related Questions