Skip to content

Instantly share code, notes, and snippets.

@richy486
Last active December 7, 2018 15:29
Show Gist options
  • Save richy486/23aa02c929bbfd1b8e16 to your computer and use it in GitHub Desktop.
Save richy486/23aa02c929bbfd1b8e16 to your computer and use it in GitHub Desktop.
//
// 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
//
// 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
@richy486
Copy link
Author

Updated for iOS 8.
Removed non-arc code.
Changed UILineBreakMode to NSLineBreakMode.
Changed sizeWithFont: to sizeWithAttributes:
Added more curly brackets to if statements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment