gcamp gcamp - 6 months ago 72
iOS Question

Blurry image (NSTextAttachment) on NSAttributedString

I'm using NSAttributedString to include images in a string. However, the images are sometimes blurry like if were draw on a non-integer frame.

I tried to make sure the bound of each NSTextAttachment is of integer size, but that doesn't seem to help. Any tips on how to make sure it's not blurry?

See attached screenshot, the first bus is not blurry but the second is.

enter image description here


I fixed this by adding a category on NSAttributedString.

Basically, you need to get the NSTextAttachment's frame and add the missing fractional part of its X coordinate to have it round up nicely.

- (void)applyBlurrinessFixToAttachments {
    [self enumerateAttribute:NSAttachmentAttributeName inRange:NSMakeRange(0, self.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
        if (![value isKindOfClass:[NSTextAttachment class]]) {
        NSTextAttachment *attachment = (NSTextAttachment*)value;
        CGRect bounds = attachment.bounds;
        CGRect attributedStringRect = [self boundingRectForCharacterRange:range];

        double integral;
        double fractional = modf(attributedStringRect.origin.x, &integral);
        double roundedXOrigin = 1.0 - fractional;

        // If X coordinate starts at 0.7, add 0.3 to it
        bounds.origin.x += roundedXOrigin;
        attachment.bounds = bounds;

- (CGRect)boundingRectForCharacterRange:(NSRange)range {
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
    [textStorage addLayoutManager:layoutManager];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
textContainer.lineFragmentPadding = 0;
    [layoutManager addTextContainer:textContainer];

    NSRange glyphRange;
    [layoutManager characterRangeForGlyphRange:range actualGlyphRange:&glyphRange];

    return [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];