Attributed text, replace a specific font by another using swift

前端 未结 2 1410
慢半拍i
慢半拍i 2021-01-16 16:25

I\'m using attributed text in a UITextView. The text contain many fonts.

I\'m looking for a way to replace a particular font by another one.

Any swift approa

相关标签:
2条回答
  • 2021-01-16 17:05

    My code will be in Objective-C, but since we use both CocoaTouch, it should be the same logic.

    The method I use is enumerateAttribute:inRange:options:usingBlock: to look only for NSFontAttributeName.

    There is another point that isn't discussed: How recognize that the font is the one searched. Are you looking for familyName, fontName (property of UIFont? Even in the same family, font may look a lot different and you may want to search really for the one exactly matching the same name. I've discussed once about Font Names here. You may found it interesting in your case. Note that there are methods (that I didn't know at the time) that can get the Bold Name of the font if available (or italic, etc.)

    The main code in Objective-C is this one:

    [attrString enumerateAttribute:NSFontAttributeName
                               inRange:NSMakeRange(0, [attrString length])
                               options:0
                            usingBlock:^(id value, NSRange range, BOOL *stop) {
                                UIFont *currentFont = (UIFont *)value; //Font currently applied in this range
                                if ([self isFont:currentFont sameAs:searchedFont]) //This is where it can be tricky
                                {
                                [attrString addAttribute:NSFontAttributeName
                                                   value:replacementFont
                                                   range:range];
                                }
        }];
    

    Possible change/adaptation according to your needs: Change the font, but not the size:

    [attrString addAttribute:NSFontAttributeName value:[UIFont fontWithName:replaceFontName size:currentFont.pointSize]; range:range];
    

    Sample test code:

    UIFont *replacementFont = [UIFont boldSystemFontOfSize:12];
    UIFont *searchedFont    = [UIFont fontWithName:@"Helvetica Neue" size:15];
    UIFont *normalFont      = [UIFont italicSystemFontOfSize:14];
    
    NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet,"];
    
    NSAttributedString *attrStr1 = [[NSAttributedString alloc] initWithString:@"consectetuer adipiscing elit." attributes:@{NSFontAttributeName:searchedFont, NSForegroundColorAttributeName:[UIColor redColor]}];
    
    NSAttributedString *attrStr2 = [[NSAttributedString alloc] initWithString:@" Aenean commodo ligula eget dolor." attributes:@{NSFontAttributeName:normalFont}];
    
    NSAttributedString *attrStr3 = [[NSAttributedString alloc] initWithString:@" Aenean massa." attributes:@{NSFontAttributeName:searchedFont}];
    
    NSAttributedString *attrStr4 = [[NSAttributedString alloc] initWithString:@"Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim."];
    
    [attrString appendAttributedString:attrStr1];
    [attrString appendAttributedString:attrStr2];
    [attrString appendAttributedString:attrStr3];
    [attrString appendAttributedString:attrStr4];
    
    NSLog(@"AttrString: %@", attrString);
    
    [attrString enumerateAttribute:NSFontAttributeName
                           inRange:NSMakeRange(0, [attrString length])
                           options:0
                        usingBlock:^(id value, NSRange range, BOOL *stop) {
                            UIFont *currentFont = (UIFont *)value;
                            if ([self isFont:currentFont sameAs:searchedFont])
                            {
                [attrString addAttribute:NSFontAttributeName
                                   value:replacementFont
                                   range:rangeEffect];
                            }
    }];
    
    NSLog(@"AttrString Changed: %@", attrString);
    

    With the solution of @TigerCoding, here is the possible code:

    NSInteger location = 0;
    while (location < [attrString length])
    {
        NSRange rangeEffect;
        NSDictionary *attributes = [attrString attributesAtIndex:location effectiveRange:&rangeEffect];
        if (attributes[NSFontAttributeName])
        {
            UIFont *font = attributes[NSFontAttributeName];
            if ([self isFont:font sameAs:searchedFont])
            {
                [attrString addAttribute:NSFontAttributeName value:replacementFont range:rangeEffect];
            }
        }
        location+=rangeEffect.length;
    }
    

    As a side note: A few optimization to test (but will need some research). I think from a few example that if you apply the same attributeS for two consecutive range, NSAttributedString will "appends them" into one, in case you may be afraid to apply the same effect consecutively. So the question is that if you have @{NSFontAttributeName:font1, NSForegroundColorAttributeName:color1} for range 0,3 and @{NSFontAttributeName:font1, NSForegroundColorAttributeName:color2} for range 3,5 Will enumerateAttribute:inRange:options:usingBlock: return you the range 0,5? Will it be faster than enumerating each indexes?

    0 讨论(0)
  • 2021-01-16 17:06

    There is a method called attributesAtIndex(_:effectiveRange:). This method returns a dictionary that you can get the font from.

    You will need to iterate over each index to store the fonts. It will be slow, because in text files the font could potentially change every character. So I would recommend doing this off the main thread.

    0 讨论(0)
提交回复
热议问题