33

All I need is to loop through all attributes of NSAttributedString and increase their font size. So far I got to the point where I successfully loop through and manipulate attributes but I cannot save back to NSAttributedString. The line I commented out is not working for me. How to save back?

NSAttributedString *attrString = self.richTextEditor.attributedText;

[attrString enumerateAttributesInRange: NSMakeRange(0, attrString.string.length)
                               options:NSAttributedStringEnumerationReverse usingBlock:
 ^(NSDictionary *attributes, NSRange range, BOOL *stop) {

     NSMutableDictionary *mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];        

     UIFont *font = [mutableAttributes objectForKey:NSFontAttributeName];
     UIFont *newFont = [UIFont fontWithName:font.fontName size:font.pointSize*2];         
     [mutableAttributes setObject:newFont forKey:NSFontAttributeName];
     //Error: [self.richTextEditor.attributedText setAttributes:mutableAttributes range:range];
     //no interfacce for setAttributes:range:

 }];
rmaddy
  • 314,917
  • 42
  • 532
  • 579
Vad
  • 3,658
  • 8
  • 46
  • 81

4 Answers4

57

Something like this should work:

NSMutableAttributedString *res = [self.richTextEditor.attributedText mutableCopy];

[res beginEditing];
__block BOOL found = NO;
[res enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, res.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
    if (value) {
        UIFont *oldFont = (UIFont *)value;
        UIFont *newFont = [oldFont fontWithSize:oldFont.pointSize * 2];
        [res removeAttribute:NSFontAttributeName range:range];
        [res addAttribute:NSFontAttributeName value:newFont range:range];
        found = YES;
    }
}];
if (!found) {
    // No font was found - do something else?
}
[res endEditing];
self.richTextEditor.attributedText = res;

At this point res has a new attributed string with all fonts being twice their original size.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • 1
    Complete solution would be adding this line to the end: self.richTextEditor.attributedText = res; Then this worked for me. – Vad Oct 15 '13 at 19:12
  • 2
    in my attribute string all fonts are different like bold, italic, underline having bullet points too. but it works very well.+1 from me dude.... – Sam Feb 12 '14 at 12:04
  • 1
    I do not know about other users, but this no longer works for me. – Micrified Jan 04 '16 at 20:26
  • Small tip: To change font size in the whole string do not set found to YES. – Vladimir Vodolazkiy Jun 01 '16 at 13:43
  • A more current approach to this using TextKit would be to call this method on the text view's text storage, i.e. `[self.richTextEditor.textStorage enumerateAttribute:inRange:options:usingBlock:]`. `beginEditing` and `endEditing` would be called on the text storage as well. – Ben Kane Oct 25 '16 at 15:10
5

Create an NSMutableAttributedString from your original attributed string before you start. On each iteration of the loop, call addAttribute:value:range: on the mutable attributed string (this will replace the old attributes in that range).

Wain
  • 118,658
  • 15
  • 128
  • 151
  • The first part you suggest seems to be done: I created attributed string. Or should I create it from string? Second part has the same problem: addAttribute:value:range: is not a method for NSAttributedString. Am I doing something wrong? – Vad Oct 15 '13 at 17:17
  • Your solution is correct. Thank you. However, I could find more useful points from rmaddy's example that specifically fit my needs. You put me on the right track though. Thanks. – Vad Oct 16 '13 at 01:30
4

Here is a Swift port of maddy's answer (which works really well for me!). It's wrapped in a little extension.

import UIKit

extension NSAttributedString {
    func changeFontSize(factor: CGFloat) -> NSAttributedString {
        guard let output = self.mutableCopy() as? NSMutableAttributedString else {
            return self
        }

        output.beginEditing()
        output.enumerateAttribute(NSAttributedString.Key.font,
                                  in: NSRange(location: 0, length: self.length),
                                  options: []) { (value, range, stop) -> Void in
            guard let oldFont = value as? UIFont else {
                return
            }
            let newFont = oldFont.withSize(oldFont.pointSize * factor)
            output.removeAttribute(NSAttributedString.Key.font, range: range)
            output.addAttribute(NSAttributedString.Key.font, value: newFont, range: range)
        }
        output.endEditing()

        return output
    }
}
Oliver
  • 251
  • 3
  • 6
0

if You need the exact opposite behaviour, i.e. change font but keeping other attributes, see here:

NSAttributedString, change the font overall BUT keep all other attributes?

ingconti
  • 10,876
  • 3
  • 61
  • 48