Search code examples
iosswift2nsattributedstring

Get blocks of text from NSAttributedString


If i get an attributed string from a text field using

let text = input.attributedText!
print(text)

in Swift, then the output is as follows (when the input contains "hello" in regular then "world" in bold)

    hello {
    NSColor = "UIDeviceWhiteColorSpace 0 1";
    NSFont = "<UICTFont: 0x134544930> font-family: \".SFUIText-Regular\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 2, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection 0, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0";
    NSShadow = "NSShadow {0, -1} color = {(null)}";
}
    world{
    NSColor = "UIDeviceWhiteColorSpace 0 1";
    NSFont = "<UICTFont: 0x1345b12a0> font-family: \".SFUIText-Bold\"; font-weight: bold; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 2, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection 0, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0";
    NSShadow = "NSShadow {0, -1} color = {(null)}";
}

I can see that the two blocks of differently formatted writing are represented in two blocks when printed to the console. Now what I want to do is loop through all the blocks and for each one, get the text and the font. So in this case, the first time it loops, it would find "Hello" and " font-family: \".SFUIText-Regular\"; font-weight: normal; font-style: normal; font-size: 17.00pt" and the second time it'd find "world" and it's font

I can loop through fonts using the code

text.enumerateAttribute(NSFontAttributeName, inRange: NSMakeRange(0, text.length), options: NSAttributedStringEnumerationOptions()) { (font: AnyObject?, range: NSRange, usmp: UnsafeMutablePointer<ObjCBool>) -> Void in

        print(font)
    }

Is there a way to do the same for the actual text?


Solution

  • Yes - just enumerate all of the attributes instead of just one.

    Assuming you have an attributed string like this:

    // Create the string
    
    let text = NSMutableAttributedString(string: "hello world")
    let font = UIFont(name: ".SFUIText-Regular", size: 17)!
    let boldFont = UIFont(name: ".SFUIText-Bold", size: 17)!
    
    text.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(), range: NSMakeRange(0, text.string.characters.count))
    
    text.addAttribute(NSFontAttributeName, value: font, range: NSMakeRange(0, 6))
    text.addAttribute(NSFontAttributeName, value: boldFont, range: NSMakeRange(6, 5))
    

    You can create output similar to the one in your example like this:

    // Enumerate the attributes
    
    text.enumerateAttributesInRange(NSMakeRange(0, text.string.characters.count), options: []) { (attribute, range, stop) -> Void in
        let substring = (text.string as NSString).substringWithRange(range)
        debugPrint(substring, attribute)
    }
    

    The output looks like this:

    "hello " ["NSFont": <UICTFont: 0x7fba19724be0> font-family: ".SFUIText-Regular"; font-weight: normal; font-style: normal; font-size: 17.00pt, "NSColor": UIDeviceWhiteColorSpace 0 1]
    "world" ["NSFont": <UICTFont: 0x7fba1960d8d0> font-family: ".SFUIText-Bold"; font-weight: bold; font-style: normal; font-size: 17.00pt, "NSColor": UIDeviceWhiteColorSpace 0 1]