cusquinho
cusquinho

Reputation: 387

iOS UITableView with dynamic text and images rendered together (NSAttributedString + images)

My problem is this: I have dynamic content in an iOS app (such as twits - although this is not a twitter app) that include both text and images (mostly icons/emoticons and thumbnails). I want to render both text and images together in a table row. The main difficulty here is that each row will have a different size (making caching harder), and I need to calculate image size dynamically to fit text around on each image occurrence (I can have like 20 emoticons one next to another + text + more emoticons + etc.).

I've been looking around and I've tried a few approaches. My initial idea was to use UIWebView. I was able to create a sample app with one UIWebView per row, and even with some "smart" NSCache for pre-rendered cells performance was not very good, plus I had to deal with UIWebView javascript callbacks to figure out when content was properly loaded.

My second attempt is to overwrite drawRect for a new UITableViewCell. Inside the drawRect method I'm using NSAttributedString and the NSSelectorFromString to set the ranges for each kind of content (regular text, bold, italic, different color, images). To fit the images I'm using the CTRunDelegateCallbacks callbacks (getAscent, getDescent, getWidth). Something like this:

            CTRunDelegateCallbacks callbacks;
            callbacks.version = kCTRunDelegateVersion1;
            callbacks.getAscent = ascentCallback;
            callbacks.getDescent = descentCallback;
            callbacks.getWidth = widthCallback;

            CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)imgAttr); // img Attr is a Dictionary with all image info

For more details on this approach please check this excellent post: http://www.raywenderlich.com/4147/how-to-create-a-simple-magazine-app-with-core-text#

Finally, two questions:

--- Adding more info

After my drawRect creates all content (strings and images) I'm trying to use boundingRectWithSize to get the proper height:

CGRect frame = [self.attString boundingRectWithSize:CGSizeMake(320, 10000) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading | NSStringDrawingUsesDeviceMetrics context:nil];

This works fine if it's text only, but when I add the image glyphs to the content it does not calculate the height properly...

Upvotes: 4

Views: 2284

Answers (2)

I ran into the same issue und solved it today. I am drawing custom objects between the text with the CTRunDelegateCallbacks, but boundingRectWithSize:options:context:did not give me the correct height. (It ignores my custom objects).

Instead I found a lower level API in Core Text, which fulfils all requirements and is totally correct. I created a category for that issue. Have fun using it! :-)

Header file:

#import <Foundation/Foundation.h>

@interface NSAttributedString (boundsForWidth)

- (CGRect)boundsForWidth:(float)width;

@end

Implementation file:

#import "NSAttributedString+boundsForWidth.h"
#import <CoreText/CoreText.h>

@implementation NSAttributedString (boundsForWidth)

- (CGRect)boundsForWidth:(float)width
{
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)self);

    CGSize maxSize = CGSizeMake(width, 0);

    CGSize size = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, maxSize, nil);

    CFRelease(framesetter);

    return CGRectMake(0, 0, ceilf(size.width), ceilf(size.height));
}

@end

Upvotes: 3

Timothy Moose
Timothy Moose

Reputation: 9915

This post may get you going in the right direction for calculating row height:

Retrieve custom prototype cell height from storyboard?

As for performance, have you run Time Profiler to narrow down what is causing lag?

Upvotes: 2

Related Questions