How to resize and layout image/attachment in TextKit?

660 Views Asked by At

I'm building a magazine app via TextKit, here is a TextKit demo (check out the developer branch). It loads a NSAttributeString from a rtfd file as text storage object, all the pages have the same size with custom NSTextContainer object, the pagination feature is done.

When I tried to add image to the source rtfd file, the image attachment shows in UITextView directly without any additional code, that's great! However, some big images will be clipped by default in text view frame. I tried all kinds of delegate methods and override methods to resize and re-layout it but failed at the end.

 - (void)setAttachmentSize:(CGSize)attachmentSize forGlyphRange:(NSRange)glyphRange
 - (CGSize)attachmentSizeForGlyphAtIndex:(NSUInteger)glyphIndex;

The setter method is called in glyph layout process, the latter getter method is called in glyph draw process from the call stack.

- (BOOL)layoutManager:(NSLayoutManager *)layoutManager shouldSetLineFragmentRect:(inout CGRect *)lineFragmentRect lineFragmentUsedRect:(inout CGRect *)lineFragmentUsedRect baselineOffset:(inout CGFloat *)baselineOffset inTextContainer:(NSTextContainer *)textContainer forGlyphRange:(NSRange)glyphRange 
{

    NSTextAttachment *attachment = ...;

    NSUInteger characterIndex = [layoutManager characterIndexForGlyphAtIndex:glyphRange.location];
    UIImage *image = [attachment imageForBounds:*lineFragmentRect textContainer:textContainer characterIndex:characterIndex];
    CGSize imageSize = GetScaledToFitSize(image.size, self.textContainerSize);

    CGFloat ratio = imageSize.width / imageSize.height;
    CGRect rect = *lineFragmentRect, usedRect = *lineFragmentUsedRect;

    CGFloat dy = *baselineOffset - imageSize.height;

    if (dy > 0) {
        *baselineOffset -= dy;
        usedRect.size.height -= dy;
        usedRect.size.width = ratio * usedRect.size.height;
    }

    if (!CGRectContainsRect(usedRect, rect)) {
        if (rect.size.height > usedRect.size.height) {
            *baselineOffset -= rect.size.height - usedRect.size.height;
            rect.size.height = usedRect.size.height;
            rect.size.width = ratio * usedRect.size.height;
        }

        if (rect.size.width > usedRect.size.width) {
            //...
        }
    }

    *lineFragmentRect = rect;
    *lineFragmentUsedRect = usedRect;
    
    return YES;
 }

This delegate method could resize the layout size but not affect to the final width and image scale. I tried serval solutions with no luck. It seems there aren't many threads about images on TextKit on SO and Apple example code.

1

There are 1 best solutions below

0
On

I have ever done similar work for image attachment auto resize. How about handle it just after you get the attributed string.

That is, enumerate the original string with NSAttachmentAttributeName, replace the attachment with a subclass of NSTextAttachment, with implies NSTextAttachmentContainer protocol.

- (CGRect)attachmentBoundsForTextContainer:(nullable NSTextContainer *)textContainer proposedLineFragment:(CGRect)lineFrag glyphPosition:(CGPoint)position characterIndex:(NSUInteger)charIndex {
    CGFloat lineWidth = CGRectGetWidth(lineFrag);
    CGSize size = self.bounds.size;
    size.height *= (size.width > 0) ? (lineWidth / size.width) : 0;
    size.width = lineWidth;
    return CGRectMake(0, 0, size.width, size.height);
}

Code above resize attachment to fit the width, you don't need to resize image, since it will be auto resize to the bound when drawing.

Hoping to be helpful.