Truncate a String and Append an Ellipsis, Respecting the Font Size

A number of the UI related controls will automatically truncate and append ellipsis with no effort required on your part. For example, with UILabel you can specify the linebreak mode to indicate how you would like the system to manage wrapping and truncating the label text.

However, they are times when having a method to truncate a string without using a UI control would be handy. In this post I’ll create a category which will add a method to the NSString class to do just that. You can read more about working with categories in this post: Introduction to Categories.

NSString Category

Start by creating a new interface file with the name: NSString+TruncateToWidth.h – appending the method name to the class is a common naming convention when working with categories.

The code below defines a method that will be available to all NSString objects. The method will truncate a string to a specific width, using the font size to properly determine the string size before truncating:

Code to Truncate NSString

The implementation of the stringByTruncatingToWidth method follows, add this code to the NSString+TruncateToWidth.m file.

Notice that the desired final width of the string is passed in, as well as the font that will be used when calculating the size (width) of the string. The process is quite simple: change the incoming width to accommodate an ellipsis on the end of the string – loop through the string removing characters from the end until the desired width is reached – append an ellipsis onto the string.

Calling the Truncate NSString Method

You can now call the method stringByTruncatingToWidth:withFont: on any NSString object:

The output from above would look as follows:

Lorem ipsum dolor sit…

12 Comments

  1. Does NSString mutableCopy really return a string that needs to be released? By standard naming conventions, it should not.

    You might want to generalize the string to be appended.

    – (NSString*)stringByTruncatingStringToWidth:(CGFloat)width withFont:(UIFont *)font endingWith:(NSString*)endString { /* your implementation but using endString */ }

    and implement your function as

    – (NSString*)stringByTruncatingStringToWidth:(CGFloat)width withFont:(UIFont *)font
    {
    [self stringByTruncatingStringToWidth: width withFont: font endingWith: ellipsis];
    }

    But that’s just a small tweak.

  2. Michelle, the docs state “If you are using managed memory (not garbage collection), this method retains the new object before returning it.” so the autorelease should do the trick…

  3. Thank you for this post!
    I have a problem with this implementation: Strings are cut, even if they are narrower
    than the defined maximum width:

    Example:
    cell.textLabel.text = [news stringByTruncatingToWidth:240 withFont:[UIFont boldSystemFontOfSize:15]];

    So every String is at least missing the last character and gets the ellipsis appended…

    “Here is a superlong Title to…” -> correct
    “Short Titl…” -> wrong

    Cheers
    Krille

    • Krille, thanks for pointing that out. I’ve updated the code to check for the string length up front.

  4. Great – thank you very much, everytime I learn something new from your articles!

  5. Hello. Thank you for that. Is there any way to truncate multiline text? I mean adding to your category method like – (NSString*)stringByTruncatingToSize:(CGSize)size withFont:(UIFont *)font; Is it possible?
    Thanks in advance.

    • I wrote a simple method that returns a substring that fits into a multi-line UITextView. Not pretty, but:

      – (NSString*)stringByTruncatingToWidth:(NSString *)incoming desiredSize:(CGSize)size withFont:(UIFont *)font {
      NSMutableString *truncatedString = [incoming mutableCopy];
      NSString *theMore = @” More ..”;
      NSMutableString *truncatedPlus = (NSMutableString *)[truncatedString stringByAppendingString:theMore];

      // someone told me “UITextView has 8px of padding on each side” (because it inherits from UIScrollView?)
      size.width -= 16.0;
      CGSize bigBox = CGSizeMake(size.width, MAXFLOAT);

      if ([incoming sizeWithFont:font constrainedToSize:bigBox].height > [incoming sizeWithFont:font constrainedToSize:size].height) {
      NSRange range = {truncatedString.length – 1, 1};

      while ([truncatedPlus sizeWithFont:font constrainedToSize:bigBox].height > size.height) {
      [truncatedString deleteCharactersInRange:range];
      truncatedPlus = (NSMutableString *)[truncatedString stringByAppendingString:theMore];
      range.location–;
      }
      [truncatedString replaceCharactersInRange:range withString:theMore];
      }
      return truncatedString;
      }

  6. how would you do the same thing for multiple lines where the ellipsis is only added to the last line?

  7. Thanks John, this was so useful.

    In my case, when we build projects we normally have this utility class. Instead of creating this new class, I added the methods above as a static (+) method
    header file:

    + (NSString *)stringByTruncatingToWidth:(CGFloat)width withFont:(UIFont *)font withString:(NSString *)string;

    implementation file:
    + (NSString *)stringByTruncatingToWidth:(CGFloat)width withFont:(UIFont *)font withString:(NSString *)string {
    // Create copy that will be the returned result
    NSMutableString *truncatedString = [[string mutableCopy] autorelease];

    // Make sure string is longer than requested width
    if ([string sizeWithFont:font].width > width)
    {
    // Accommodate for ellipsis we’ll tack on the end
    width -= [ellipsis sizeWithFont:font].width;

    // Get range for last character in string
    NSRange range = {truncatedString.length – 1, 1};

    // Loop, deleting characters until string fits within width
    while ([truncatedString sizeWithFont:font].width > width)
    {
    // Delete character at end
    [truncatedString deleteCharactersInRange:range];

    // Move back another character
    range.location–;
    }

    // Append ellipsis
    [truncatedString replaceCharactersInRange:range withString:ellipsis];
    }

    return truncatedString;
    }

    usage:
    [DeviceUtility stringByTruncatingToWidth:myTextLabel.frame.size.width withFont:myFont withString:myString];

    Hope this helps someone too.

  8. Thanks for posting your snazzy code. You saved me a good half hour.

  9. Thank you for the post, it helped me fit long descriptions into back buttons for a custom titleView.

    I found however that it was very inefficient in these circumstances, so I decided to add the caveat that if the width of the original string is twice the length of the desired length, it should go from the other end.

    Have tested this code and it seems to work well:

    – (NSString*)stringByTruncatingToWidth:(CGFloat)width withFont:(UIFont *)font
    {
    // Create copy that will be the returned result
    NSMutableString *truncatedString = [self mutableCopy];

    // Make sure string is longer than twice requested width, then approach truncation from Right hand Side
    // Else if string is long than width but not twice, approach truncation from Left hand side.
    if ([self sizeWithFont:font].width > 2*width) {
    // Accommodate for ellipsis we’ll tack on the end
    width -= [ellipsis sizeWithFont:font].width;

    // Create new string as we are coming from the other end!
    truncatedString = [NSMutableString string];

    // We need the range of the first character in string
    NSRange range = {0, 1};

    // Loop, adding characters until string fits within width
    while ([truncatedString sizeWithFont:font].width width) {
    // Accommodate for ellipsis we’ll tack on the end
    width -= [ellipsis sizeWithFont:font].width;

    // Get range for last character in string
    NSRange range = {truncatedString.length – 1, 1};

    // Loop, deleting characters until string fits within width
    while ([truncatedString sizeWithFont:font].width > width)
    {
    // Delete character at end
    [truncatedString deleteCharactersInRange:range];

    // Move back another character
    range.location–;
    }
    // Append ellipsis
    [truncatedString replaceCharactersInRange:range withString:ellipsis];
    }

    return truncatedString;
    }

Comments are closed.