70

I've created a method that takes a NSAttributedString and I'm looking to dynamically create a subview and label to put the string into. Because attributes like font and size need to be determined to correctly determine the size of the label, I need to determine if it is possible to iterate through values and ranges that have been applied to the attributed string?

I understand that I could pass the attributes separately, but for sake of reusability, i'd like to be able to pass as few parameters to the method as possible.

Georgi
  • 674
  • 7
  • 21
propstm
  • 3,461
  • 5
  • 28
  • 41
  • 1
    A simple look at the docs would have revealed `enumerateAttributesInRange:options:usingBlock:`. – rmaddy Nov 07 '13 at 19:06

7 Answers7

104

Swift 5 – 4

let attributedText = NSAttributedString(string: "Hello, playground", attributes: [
  .foregroundColor: UIColor.red, 
  .backgroundColor: UIColor.green, 
  .ligature: 1, 
  .strikethroughStyle: 1
])

// retrieve attributes
let attributes = attributedText.attributes(at: 0, effectiveRange: nil)

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

In case, that you have defined attributedText of label.

Swift 3

var attributes = attributedText.attributes(
  at: 0, 
  longestEffectiveRange: nil, 
  in: NSRange(location: 0, length: attributedText.length)
)

Swift 2.2

var attributes = attributedText.attributesAtIndex(0,   
  longestEffectiveRange: nil, 
  inRange: NSMakeRange(0, attributedText.length)
)
dimpiax
  • 12,093
  • 5
  • 62
  • 45
  • 6
    Could you please explain why `effectiveRange` parameter gets `nil` argument in your examples? Why shouldn't it be set to the total range of characters within the string? Thanks. – Max Desiatov Oct 17 '18 at 10:38
  • 3
    @MaxDesiatov if you read the docs, it states that "Upon return, the range over which the attributes and values are the same as those at index. This range isn’t necessarily the maximum range covered, and its extent is implementation-dependent. If you need the maximum range, use attributes(at:longestEffectiveRange:in:). If you don't need this value, pass NULL". We don't need this value, so we don't capture it. – Allison Jul 12 '19 at 21:24
32

Apple expects you to use enumerateAttributesInRange:options:usingBlock:. The block you supply will receive ranges and the attributes applicable for that range.

I've used that in my code to create invisible buttons that are placed behind text so that it acts as a hyperlink.

You could also use enumerateAttribute:inRange:options:usingBlock: if there's only one you're interested in, but no halfway house is provided where you might be interested in, say, two attributes but not every attribute.

Tommy
  • 99,986
  • 12
  • 185
  • 204
13

If you need all of the attributes from the string in the entire range, use this code:

NSDictionary *attributesFromString = [stringWithAttributes attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, stringWithAttributes.length)];

TheJeff
  • 3,665
  • 34
  • 52
  • This way you got only attributes for character/substring at 0th index - not all attributes for whole string. – Palli Mar 24 '23 at 11:38
4

Swift 3:

// in case, that you have defined `attributedText` of label
var attributes = attributedText.attributes(at: 0, longestEffectiveRange: nil, in: NSMakeRange(0, attributedText.length))
...
Trein
  • 3,658
  • 27
  • 36
4

Swift 4:

If you want to get the attributes for NSTextAttachment (just change the attribute value if you want font)

commentTextView.attributedText.enumerateAttribute(NSAttachmentAttributeName,
                        in:NSMakeRange(0, commentTextView.attributedText.length),
                        options:.longestEffectiveRangeNotRequired) {
                        value, range, stop in
                            if let attachment = value as? NSTextAttachment {
                                print("GOT AN ATTACHMENT! for comment at \(indexPath.row)")
                            }
}
Josh O'Connor
  • 4,694
  • 7
  • 54
  • 98
3

Apple's Docs have a number of methods to access attributes:

To retrieve attribute values from either type of attributed string, use any of these methods:

attributesAtIndex:effectiveRange: attributesAtIndex:longestEffectiveRange:inRange: attribute:atIndex:effectiveRange: attribute:atIndex:longestEffectiveRange:inRange: fontAttributesInRange: rulerAttributesInRange:

paulrehkugler
  • 3,241
  • 24
  • 45
-1

I have modified one of the answers with suggested fixes to prevent infinite recursion and application crashes.

@IBDesignable
extension UITextField{

    @IBInspectable var placeHolderColor: UIColor? {
        get {
            if let color = self.attributedPlaceholder?.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? UIColor {
                return color
            }
            return nil
        }
        set {
            self.attributedPlaceholder = NSAttributedString(string:self.placeholder != nil ? self.placeholder! : "", attributes:[.foregroundColor: newValue!])
        }
    }
}
Michał Ziobro
  • 10,759
  • 11
  • 88
  • 143