Truncate string containing emoji or unicode characters at word or character boundaries

emoji, ios, nsstring, objective-c, unicode

Solution

`NSString` has a method `rangeOfComposedCharacterSequencesForRange` that you can use to find the enclosing range in the string that contains only complete composed characters. For example

NSString *s =  @"";
NSRange r = [s rangeOfComposedCharacterSequencesForRange:NSMakeRange(0, 1)];

gives the range `{ 0, 2 }` because the Emoji character is stored as two UTF-16 characters (surrogate pair) in the string.

Remark: You could also check if you can simplify your first loop by using

enumerateSubstringsInRange:options:usingBlock

with the `NSStringEnumerationByWords` option.

Problem

How can I truncate a string at a given length without annihilating a unicode character that might be smack in the middle of my length? How can one determine the index of the beginning of a unicode character in a string so that I can avoid creating ugly strings. The square with half of an A visible is the location of another emoji character which has been truncated. ``` -(NSMutableAttributedString*)constructStatusAttributedStringWithRange:(CFRange)range NSString *original = [_postDictionay objectForKey:@"message"]; NSMutableString *truncated = [NSMutableString string]; NSArray *components = [original componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; for(int x=0; x<[components count]; x++) { //If the truncated string is still shorter then the range desired. (leave space for ...) if([truncated length]+[[components objectAtIndex:x] length]<range.length-3) { //Just checking if its the first word if([truncated length]==0 && x==0) { //start off the string [truncated appendString:[components objectAtIndex:0]]; } else { //append a new word to the string [truncated appendFormat:@" %@",[components objectAtIndex:x]]; } } else { x=[components count]; } } if([truncated length]==0 || [truncated length]< range.length-20) { truncated = [NSMutableString stringWithString:[original substringWithRange:NSMakeRange(range.location, range.length-3)]]; } [truncated appendString:@"..."]; NSMutableAttributedString *statusString = [[NSMutableAttributedString alloc]initWithString:truncated]; [statusString addAttribute:(id)kCTFontAttributeName value:[StyleSingleton streamStatusFont] range:NSMakeRange(0, [statusString length])]; [statusString addAttribute:(id)kCTForegroundColorAttributeName value:(id)[StyleSingleton streamStatusColor].CGColor range:NSMakeRange(0, [statusString length])]; return statusString; } ``` UPDATE Thanks to the answer, was able to use one simple function for my needs! ``` -(NSMutableAttributedString*)constructStatusAttributedStringWithRange:(CFRange)range { NSString *original = [_postDictionay objectForKey:@"message"]; NSMutableString *truncated = [NSMutableString stringWithString:[original substringWithRange:[original rangeOfComposedCharacterSequencesForRange:NSMakeRange(range.location, range.length-3)]]]; [truncated appendString:@"..."]; NSMutableAttributedString *statusString = [[NSMutableAttributedString alloc]initWithString:truncated]; [statusString addAttribute:(id)kCTFontAttributeName value:[StyleSingleton streamStatusFont] range:NSMakeRange(0, [statusString length])]; [statusString addAttribute:(id)kCTForegroundColorAttributeName value:(id)[StyleSingleton streamStatusColor].CGColor range:NSMakeRange(0, [statusString length])]; return statusString; } ```

Original source