Replace regex matches in attributed string with im

2019-04-13 04:55发布

My goal is to store the information for an attributed string in Parse.com. I decided to come up with an encoding for attributed text for my images that works by replacing any string {X} in braces with the corresponding image. For example:

Picture of 2 colorless mana: {X}

Should produce an attributed string where {X} is replaced by an image. This is what I've tried:

NSString *formattedText = @"This will cost {2}{PW}{PW} to cast.";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(?<=\\{)[^}]+(?=\\})" options:NSRegularExpressionAnchorsMatchLines
                                                                         error:nil];
NSArray *matches = [regex matchesInString:formattedText
                                  options:kNilOptions
                                    range:NSMakeRange(0, formattedText.length)];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:formattedText];
for (NSTextCheckingResult *result in matches)
{
    NSString *match = [formattedText substringWithRange:result.range];
    NSTextAttachment *imageAttachment = [NSTextAttachment new];
    imageAttachment.image = [UIImage imageNamed:[NSString stringWithFormat:@"Mana%@.png", match]];
    NSAttributedString *replacementForTemplate = [NSAttributedString attributedStringWithAttachment:imageAttachment];
    [attributedString replaceCharactersInRange:result.range
                          withAttributedString:replacementForTemplate];
}
[_textView setAttributedText:attributedString];

There are two problems with this approach currently:

  • The braces aren't replaced, only the text inside of them.
  • The range for each match is changing because the string itself is changing, and it gets more off with each replacement whose original text was of length > 1. Here's what it looks like:

an image http://i57.tinypic.com/idgeqh.png

1条回答
唯我独甜
2楼-- · 2019-04-13 05:39

Two problems:

Braces aren't replaced. That's because you're using assertions, which aren't counted as part of the match. The match you're making with your pattern only contains the stuff inside the braces. Use this pattern instead:

\{([^}]+)\}

That's: match a brace, followed by one or more things that aren't closing braces in a capture group, followed by a closing brace. The whole match includes the braces now.

That introduces another problem, though -- you're using the enclosed bits to pick the replacement image. Small change to fix this: the internal capture group holds that information, now, rather than the whole group. The capture group's length tells you the range of the substring you need.

NSUInteger lengthOfManaName = [result rangeAtIndex:1].length;
NSString manaName = [match substringWithRange:(NSRange){1, lengthOfManaName}];
imageAttachment.image = [UIImage imageNamed:[NSString stringWithFormat:@"Mana%@.png", manaName]];

Second problem: length of string is changing. Just enumerate backwards:

for (NSTextCheckingResult *result in [matches reverseObjectEnumerator])
{
    //...
}

Changes to ranges towards the end of the string now won't affect earlier ranges.

查看更多
登录 后发表回答