1951FDG
2/13/2015 - 11:13 AM

NSString-truncateToSize.h

//
//  NSString-truncateToSize
//  Fast Fonts
//
//  Created by Stuart Shelton on 28/03/2010.
//  Copyright 2010 Stuart Shelton.
//
//  NSString truncate function for Objective C / iPhone SDK by
//  Stuart Shelton is licensed under a Creative Commons Attribution 3.0
//  Unported License (CC BY 3.0)
//
//  http://creativecommons.org/licenses/by/3.0/
//

#import "NSString-truncateToSize.h"

@implementation NSString (truncateToSize)

- (NSString *)truncateToSize: (CGSize)size withFont: (UIFont *)font lineBreakMode: (NSLineBreakMode)lineBreakMode {
    return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode withStartingAnchor: nil withEndingAnchor: nil];
} /* (NSString *)truncateToSize: withFont: lineBreakMode: */

- (NSString *)truncateToSize: (CGSize)size withFont: (UIFont *)font lineBreakMode: (NSLineBreakMode)lineBreakMode withAnchor: (NSString *)anchor {
    return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode withStartingAnchor: anchor withEndingAnchor: anchor];
} /* (NSString *)truncateToSize: withFont: lineBreakMode: withAnchor: */

- (NSString *)truncateToSize: (CGSize)size withFont: (UIFont *)font lineBreakMode: (NSLineBreakMode)lineBreakMode withStartingAnchor: (NSString *)startingAnchor withEndingAnchor: (NSString *)endingAnchor {
    
    
    if( !( lineBreakMode & ( NSLineBreakByTruncatingHead | NSLineBreakByTruncatingMiddle | NSLineBreakByTruncatingTail ) ) ) {
        return self;
    }
    
    if( [self sizeWithAttributes:@{NSFontAttributeName: font}].width <= size.width ) {
        return self;
    }
    
    NSString *ellipsis = @"…";

    // Note that this code will find the first occurrence of any given anchor,
    // so be careful when choosing anchor characters/strings...
    NSInteger start;
    if( startingAnchor ) {
        start = [self rangeOfString: startingAnchor options: NSLiteralSearch].location;
        if( NSNotFound == start ) {
            if( [startingAnchor isEqualToString: endingAnchor] ) {
                return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode];
            } else {
                return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode withAnchor: endingAnchor];
            }
        }
    } else {
        start = 0;
    }
    
    NSUInteger end;
    if( endingAnchor ) {
        end = [self rangeOfString: endingAnchor options: NSLiteralSearch range: NSMakeRange( start + 1, [self length] - start - 1 )].location;
        if( NSNotFound == end ) {
            if( [startingAnchor isEqualToString: endingAnchor] ) {
                // Shouldn't ever occur, since filtered out in block above...
                return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode];
            } else {
                return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode withAnchor: startingAnchor];
            }
        }
    } else {
        end = [self length];
    }

    NSUInteger targetLength = end - start;
    if( [[self substringWithRange: NSMakeRange( start, targetLength )] sizeWithAttributes:@{NSFontAttributeName: font}].width < [ellipsis sizeWithAttributes:@{NSFontAttributeName: font}].width ) {
        if( startingAnchor || endingAnchor ) {
            return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode];
        } else {
            return self;
        }
    }
    NSMutableString *truncatedString = [[NSMutableString alloc] initWithString: self];
    
    switch( lineBreakMode ) {
        case NSLineBreakByTruncatingHead:
            // Avoid anchor...
            if( startingAnchor ) {
                start++;
            }
            while( targetLength > [ellipsis length] + 1 && [truncatedString sizeWithAttributes:@{NSFontAttributeName: font}].width > size.width) {
                // Replace our ellipsis and one additional following character with our ellipsis
                NSRange range = NSMakeRange( start, [ellipsis length] + 1 );
                [truncatedString replaceCharactersInRange: range withString: ellipsis];
                targetLength--;
            }
            break;
            
        case NSLineBreakByTruncatingMiddle:
        {
            NSUInteger leftEnd = start + ( targetLength / 2 );
            NSUInteger rightStart = leftEnd + 1;

            if( leftEnd + 1 <= rightStart - 1 ) {
                break;
            }
            
            // leftPre and rightPost consist of any characters before and beyond
            // any specified anchor(s).
            // left and right are the two halves of the string to be truncated - although
            // the initial split is still performed based upon the length of the
            // (sub)string to be truncated, so we could still make a bad initial split given
            // a short string with predominantly narrow characters on one side and wide
            // characters on the other.
            NSString *leftPre = @"";
            if( startingAnchor ) {
                leftPre = [truncatedString substringWithRange: NSMakeRange( 0,  start + 1 )];
            }
            NSMutableString *left = [NSMutableString stringWithString: [truncatedString substringWithRange: NSMakeRange( ( startingAnchor ? start + 1 : start ), leftEnd - start )]];
            NSMutableString *right = [NSMutableString stringWithString: [truncatedString substringWithRange: NSMakeRange( rightStart, end - rightStart )]];
            NSString *rightPost = @"";
            if( endingAnchor ) {
                rightPost = [truncatedString substringWithRange: NSMakeRange( end, [truncatedString length] - end )];
            }
            
            /* NSLog( @"pre '%@', left '%@', right '%@', post '%@'", leftPre, left, right, rightPost ); */
            // Reassemble substrings
            [truncatedString setString: [NSString stringWithFormat: @"%@%@%@%@%@", leftPre, left, ellipsis, right, rightPost]];
            
            while( leftEnd > start + 1 && rightStart < end + 1 && [truncatedString sizeWithAttributes:@{NSFontAttributeName: font}].width > size.width) {
                CGFloat leftLength = [left sizeWithAttributes:@{NSFontAttributeName: font}].width;
                CGFloat rightLength = [right sizeWithAttributes:@{NSFontAttributeName: font}].width;
                
                // Shorten string of longest width
                if( leftLength > rightLength ) {
                    [left deleteCharactersInRange: NSMakeRange( [left length] - 1, 1 )];
                    leftEnd--;
                } else { /* ( leftLength <= rightLength ) */
                    [right deleteCharactersInRange: NSMakeRange( 0, 1 )];
                    rightStart++;
                }
                
                /* NSLog( @"pre '%@', left '%@', right'%@', post '%@'", leftPre, left, right, rightPost ); */
                [truncatedString setString: [NSString stringWithFormat: @"%@%@%@%@%@", leftPre, left, ellipsis, right, rightPost]];
            }
        }
            break;
            
        case NSLineBreakByTruncatingTail:
            while( targetLength > [ellipsis length] + 1 && [truncatedString sizeWithAttributes:@{NSFontAttributeName: font}].width > size.width) {
                // Remove last character
                NSRange range = NSMakeRange( --end, 1);
                [truncatedString deleteCharactersInRange: range];
                // Replace original last-but-one (now last) character with our ellipsis...
                range = NSMakeRange( end - [ellipsis length], [ellipsis length] );
                [truncatedString replaceCharactersInRange: range withString: ellipsis];
                targetLength--;
            }
            break;
        case NSLineBreakByWordWrapping:
        case NSLineBreakByCharWrapping:
        case NSLineBreakByClipping:
        default:
            break;
    }
    
    NSString *result = [NSString stringWithString: truncatedString];
    return result;
} /* (NSString *)truncateToSize: withFont: lineBreakMode: withStartingAnchor: withEndingAnchor: */

@end
//
//  NSString-truncateToSize
//  Fast Fonts
//
//  Created by Stuart Shelton on 28/03/2010.
//  Copyright 2010 Stuart Shelton.
//
//  NSString truncate function for Objective C / iPhone SDK by
//  Stuart Shelton is licensed under a Creative Commons Attribution 3.0
//  Unported License (CC BY 3.0)
//
//  http://creativecommons.org/licenses/by/3.0/
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface NSString (truncateToSize)

- (NSString *)truncateToSize: (CGSize)size withFont: (UIFont *)font lineBreakMode: (NSLineBreakMode)lineBreakMode;
- (NSString *)truncateToSize: (CGSize)size withFont: (UIFont *)font lineBreakMode: (NSLineBreakMode)lineBreakMode withAnchor: (NSString *)anchor;
- (NSString *)truncateToSize: (CGSize)size withFont: (UIFont *)font lineBreakMode: (NSLineBreakMode)lineBreakMode withStartingAnchor: (NSString *)startingAnchor withEndingAnchor: (NSString *)endingAnchor;

@end