5

I am using TTTAttributedLabel to apply formatting to text, however it seems to crash because I am trying to apply formatting to a range which includes emoji. Example:

NSString *text = @"@user1234  #hashtag"; // text.length reported as 22 by NSLog as each emoji is 2 chars in length
cell.textLabel.text = text;

int length = 8;
int start = 13;

NSRange *range = NSMakeRange(start, length);

if (!NSEqualRanges(range, NSMakeRange(NSNotFound, 0))) {
    // apply formatting to TTTAttributedLabel
    [cell.textLabel addLinkToURL:[NSURL URLWithString:[NSString stringWithFormat:@"someaction://hashtag/%@", [cell.textLabel.text substringWithRange:range]]] withRange:range];
}

Note: I am passed the NSRange values from an API, as well as the text string.

In the above I am attempting to apply formatting to #hashtag. Normally this works fine, but because I have emoji involved in the string, I believe the range identified is attempting to format the emoji, as they are actually UTF values, which in TTTAttributedLabel causes a crash (it actually hangs with no crash, but...)

Strangely, it works fine if there is 1 emoji, but breaks if there are 2.

Can anyone help me figure out what to do here?

mootymoots
  • 4,545
  • 9
  • 46
  • 74
  • 1
    This would be easier to diagnose if you would show the code you're using to apply the formatting, and specifically what line of code "hangs." None of the code you've shown here references the string at all. Ideally, you should be able to create a very short program that demonstrates the problem so that others can reproduce it. – Rob Napier Mar 09 '13 at 15:48
  • added above to show me applying using TTTAttributedLabel addLinkToUrl method – mootymoots Mar 09 '13 at 16:00

2 Answers2

3

The problem is that any Unicode character in your string with a Unicode value of \U10000 or higher will appears as two characters in NSString.

Since you want to format the hashtag, you should use more dynamic ways to obtain the start and length values. Use NSString rangeOfString to find the location of the # character. Use that results and the string's length to get the needed length.

NSString *text = @"@user1234  #hashtag"; // text.length reported as 22 by NSLog as each emoji is 2 chars in length
cell.textLabel.text = text;

NSUInteger start = [text rangeOfString:@"#"];
if (start != NSNotFound) {
    NSUInteger length = text.length - start;
    NSRange *range = NSMakeRange(start, length);
    // apply formatting to TTTAttributedLabel
    [cell.textLabel addLinkToURL:[NSURL URLWithString:[NSString stringWithFormat:@"someaction://hashtag/%@", [cell.textLabel.text substringWithRange:range]]] withRange:range];
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • 1
    that works if there one hashtag, but there could be many, and that would match non hashtags. I would rather utilise the provided range, and adjust it based on emoji being there. – mootymoots Mar 09 '13 at 16:20
  • 2
    The proper approach is to scan the string for hashtags. If the string can contain more than one hashtag then you can't be hardcoding the positions. You must be scanning the string for the hashtags. Make proper use of the `NSString rangeOfStringXXX` methods to find all of them. I simply gave you an example for finding one. This is easily updated, using a loop, to find every hashtag. Obviously the determination of the length is more involved then just going to the end of the string. – rmaddy Mar 09 '13 at 16:25
  • I've added a note above, it seems to only be an issue if there is two emoji together, it works fine if there is one emoji. – mootymoots Mar 09 '13 at 16:34
  • You need to stop worrying about Emoji characters. If you ask the `NSString` for the location of the `#` character for each hashtag, it doesn't matter if there are Emoji or not. If it does, then there is likely a bug in `TTTAttributedLabel`. – rmaddy Mar 09 '13 at 16:36
  • yes, I've fixed the issue with the range. I can detect the correct location of the hashtag, and provide the correct range. It still breaks. I think TTTAttributedLabel does have an issue by the looks of it. – mootymoots Mar 09 '13 at 16:39
  • I gave up on the Three20 library a long time ago. It's a bloated pile of trouble. At least for what little I original wanted to use. – rmaddy Mar 09 '13 at 16:43
2

I assume this is from the Twitter API, and you are trying to use the entities dictionary they return. I have just been writing code to support handling those ranges along with NSString's version of the range of a string.

My approach was to "fix" the entities dictionary that Twitter return to cope with the extra characters. I can't share code, for various reasons, but this is what I did:

  1. Make a deep mutable copy of the entities dictionary.
  2. Loop through the entire range of the string, unichar by unichar, doing this:
    1. Check if the unichar is in the surrogate pair range (0xd800 -> 0xdfff).
    2. If it is a surrogate pair codepoint, then go through all the entries in the entities dictionary and shift the indices by 1 if they are greater than the current location in the string (in terms of unichars). Then increment the loop counter by 1 to skip the partner of this surrogate pair as it's been handled now.
    3. If it's not a surrogate pair, do nothing.
  3. Loop through all entities and check that none of them overrun the end of the string. They shouldn't, but just incase. I found some cases where Twitter returned duff data.

I hope that helps! I also hope that one day I can open source this code as I think it would be incredibly useful!

mattjgalloway
  • 34,792
  • 12
  • 100
  • 110
  • actually I tried this, but found an even easier solution. I'm not using twitter btw, but similar with entities. Each entity has the text for the entity, so I just searched for the range of the entity.text in the full string, and BINGO, no more problems. :) – mootymoots Mar 09 '13 at 23:11
  • Oh, great, that's fine then :-). Although I guess what if you have 2 entities with the same text but linking to different places. Still, glad you solved! – mattjgalloway Mar 10 '13 at 08:47