Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to split long NSString into pages

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.

like image 727
Olof Hedman Avatar asked Sep 28 '10 12:09

Olof Hedman


1 Answers

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.

like image 108
Jakob Egger Avatar answered Nov 15 '22 13:11

Jakob Egger