Question

I need a method like CTFrameGetVisibleStringRange that can give me the text that will be rendered in a given size supplied with a line break mode (i.e. word wrap). For example I have a long line of text.. and I have a given rectangle to draw the text wrapped in it, but wherever the text gets trunecated, I continue rendering it in another area where it left off. So I need a method like:

NSString * text = "The lazy fox jumped over the creek";
[text drawAtPoint:CGPointMake(0, 0) forWidth:20 withFont:[UIFont fontWithName:@"Arial" size:10] lineBreakMode:UILineBreakModeWordWrap];
// now I do I know how much it drew before it stopped rendering?

Anyone have any ideas?

**EDITED: Please see my solution.

Was it helpful?

Solution

I had a similar problem, and I used the solution Mike posted.

It turned out, however, that trimToWord was often giving me a few too many words than could fit on my UILabel size specified. I found out that if I changed the while loop operator to a >= and not just a >, it worked perfectly.

I also added a few ivars(chopIndex and remainingBody) that I used to get the remaining string so I could display it in my next UILabel.

Here's the solution I used.

-(NSString*) rewindOneWord:(NSString*) str{
    // rewind by one word
    NSRange lastspace = [str rangeOfString:@" " options:NSBackwardsSearch];
    if (lastspace.location != NSNotFound){
        int amount = [str length]-lastspace.location;
        chopIndex -= amount;
        return [str substringToIndex:lastspace.location];
    }else {
        // no spaces, lets just rewind 2 characters at a time
        chopIndex -= 2;
        return [str substringToIndex:[str length]-2];
    }
}

// returns only how much text it could render with the given stipulations   
-(NSString*) trimToWord:(NSString*)str sizeConstraints:(CGSize)availableSize withFont:(UIFont*)font{
    if(str == @"")
        return str;

    CGSize measured = [str sizeWithFont:font constrainedToSize:CGSizeMake(availableSize.width, CGFLOAT_MAX) lineBreakMode:UILineBreakModeWordWrap];
    // 'guess' how much we will need to cut to save on processing time
    float choppedPercent = (((double)availableSize.height)/((double)measured.height));
    if(choppedPercent >= 1.0){
        //entire string can fit in availableSize
        remainingBody = @"";
        return str;
    }

    chopIndex = choppedPercent*((double)[str length]);
    str = [str substringToIndex:chopIndex];
    // rewind to the beginning of the word in case we are in the middle of one
    do{
        str = [self rewindOneWord:str];
        measured = [str sizeWithFont:font constrainedToSize:availableSize lineBreakMode:UILineBreakModeWordWrap];
    }while(measured.height>=availableSize.height);

    //increment past the last space in the chopIndex
    chopIndex++;

    //update the remaining string
    remainingBody = [remainingBody substringFromIndex:chopIndex];

    return str;
}

OTHER TIPS

Here is a solution. It is fairly fast. It 'guesses' where to chop first and then rolls back word by word. sizewithFont calls are fairly expensive so this intial 'guess' step is important. The main method is trimToWord: sizeConstraints:withFont.

Feel free to comment on how I could improve this.

-(NSString*) rewindOneWord:(NSString*) str{
    // rewind by one word
    NSRange lastspace = [str rangeOfString:@" " options:NSBackwardsSearch];
    if (lastspace.location != NSNotFound){
        int amount = [str length]-lastspace.location;
        return [str substringToIndex:lastspace.location];
    }else {
        // no spaces, lets just rewind 2 characters at a time
        return [str substringToIndex:[str length]-2];
    }
}

// returns only how much text it could render with the given stipulations   
-(NSString*) trimToWord:(NSString*) str sizeConstraints:(CGSize) avail withFont:(UIFont*) font{
    CGSize measured = [str sizeWithFont:font constrainedToSize:CGSizeMake(avail.width, 1000000) lineBreakMode:UILineBreakModeWordWrap];
    // 'guess' how much we will need to cut to save on processing time
    float choppedPercent = (((double)avail.height)/((double)measured.height));
    if (choppedPercent >= 1.0){
        return str;
    }

    int chopIndex = choppedPercent*((double)[str length]);
    str = [str substringToIndex:chopIndex];
    // rewind to the beginning of the word in case we are in the middle of one
    str = [self rewindOneWord:str];
    measured = [str sizeWithFont:font constrainedToSize:avail lineBreakMode:UILineBreakModeWordWrap];
    while (measured.height>avail.height){
        str = [self rewindOneWord:str];
        measured = [str sizeWithFont:font constrainedToSize:avail lineBreakMode:UILineBreakModeWordWrap];
    }
    return str;
}

I do'nt think,there is subsitute for CTFrameGetVisibleStringRange, Although we can get the same with the use of the below method.

- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode

Apple Documentation

http://developer.apple.com/library/ios/#documentation/uikit/reference/NSString_UIKit_Additions/Reference/Reference.html

EDITED: The below code show my approach

NSString * text = "The lazy fox jumped over the creek";

NSArray* m_Array = [text  componentsSeparatedByCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:@" "]];

CGSize mySize = CGSizeMake(300,180);
NSMutableString* myString = [[NSMutableString alloc] initWithString:@""];

//The below code till the end of the while statement could be put in separate function.

CGSize tempSize = CGSizeMake(0,0);
NSInteger index = 0 ;
do
{
      [myString  appendString:[m_Array objectAtIndex:index]];
      tempSize  = [myString  sizeWithFont:myfont constrainedToSize: 
      CGSizeMake(mySize.width, CGFLOAT_MAX) lineBreakMode: UILineBreakModeWordWrap];
      index++;

}while(tempSize.height < mySize.height && index <= [m_Array count])

//Remove the string items from m_Array till the (index-1) index,

[self RemoveItems:m_Array tillIndex:(index-1)];//Plz define you own

//you have the myString which could be fitted in CGSizeMake(300,180);


//Now start with remaining Array items with the same way as we have done above.
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top