Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 835
jz_ Avatar asked Oct 20 '25 15:10

jz_


2 Answers

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}"]

like image 73
jz_ Avatar answered Oct 23 '25 07:10

jz_


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.

like image 44
rmaddy Avatar answered Oct 23 '25 07:10

rmaddy