views:

101

answers:

1

I have a long NSString I want to display over a couple of pages.

But to do this, I need to find out how much text will actually fit on the page.

[NSString sizeWithFont: ...] Is not enough, it will just tell me if the text fits in the rectangle or not, if its does not, it will silently truncate the string, but it won't tell me where it truncated!

I need to know the first word that does not fit on the page, so I can split the string and draw that part of it on the next page. (and repeat)

Any ideas how to solve this?

Only idea I have myself so far is to repeatedly call sizeWithFont:constrainedToSize: around the point in the string where I'm guessing the page break will be, and analyse the resulting rect, but it feels cumbersome and slow and I feel I might have additional problems getting it 100% accurate... (because of descenders, and whatnot.)

ofc, it has to be available in the public iOS SDK

Answer:

Phew, that was some hairy documentation. Here is my finished function as an example, maybe it will help someone, since there isn't much iphone-specific core text examples out there.

+ (NSArray*) findPageSplits:(NSString*)string size:(CGSize)size font:(UIFont*)font;
{
  NSMutableArray* result = [[NSMutableArray alloc] initWithCapacity:32];
  CTFontRef fnt = CTFontCreateWithName((CFStringRef)font.fontName, font.pointSize,NULL);
  CFAttributedStringRef str = CFAttributedStringCreate(kCFAllocatorDefault, 
                                                       (CFStringRef)string, 
                                                       (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:(id)fnt,kCTFontAttributeName,nil]);
  CTFramesetterRef fs = CTFramesetterCreateWithAttributedString(str);
  CFRange r = {0,0};
  CFRange res = {0,0};
  NSInteger str_len = [string length];
  do {
    CTFramesetterSuggestFrameSizeWithConstraints(fs,r, NULL, size, &res);
    r.location += res.length;
    [result addObject:[NSNumber numberWithInt:res.length]];
  } while(r.location < str_len);
//  NSLog(@"%@",result);
  CFRelease(fs);
  CFRelease(str);
  CFRelease(fnt);
  return result;
}  

IMPORTANT NOTE:

You can not use the returned range or size with any UIKit classes or string drawing functions! You must only use it with Core Text, for example creating a CTFrame and drawing that. Subtle differences in things like kerning makes it impossible to combine Core Text functions with UIKit.

Also, note that the returned size has been found to be buggy.

+1  A: 

You should look into CoreText, available since iOS 3.2. The docs are rather complicated and can be found here. Basically you create a CTFramesetter and call CTFramesetterSuggestFrameSizeWithConstraints. You will then find the range of text that fits within a given CGSize.

Jakob Egger
Thanks! it indeed seems to be what I was looking for. I found the CoreText docs, but didn't see the CTFramesetterSuggest... And I got confused over the relation between NSString and CFAttributetStringRef. At the moment I have a working sollution with sizeWithFont, but I will definitely look into changing to CoreText later
Olof Hedman
There is some more information available here: http://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/CustomTextProcessing/CustomTextProcessing.html#//apple_ref/doc/uid/TP40009542-CH4-SW1
Jakob Egger
keep in mind that every nsstring* is the same as a cfstringref (toll free bridged).
Jakob Egger
CTFramesetterSuggestFrameSizeWithConstraints seems to be buggy, at least in iOS 3.2, see here: http://web.archiveorange.com/archive/v/nagQXwVJ6Gzix0veMh09
Łukasz Sromek
When I just want to split into pages and don't need to layout more then one frame in a page, I can just ignore the returned size, and it seems to work fine.
Olof Hedman