Get all available Emoji using the Objective-C runtime
- 5 Min. Read.
I recently found myself in a situation where I had to display a list of all available emoji on the screen, but Apple forgot to provide a documented way of doing this. After inspecting some of the iOS 9 runtime headers I created a small helper class which would list all Emoji (variations included), ordered by category. TLDR; You can find the sample code on github.
Update for iOS 10
iOS 10 includes emojis (Take a look at the changelog for all the details.) Updates include a new flat style for some emoji, more females and lots & lots of redesigned emoji. The helper provided is also compatible with iOS 10!
Update for iOS 9.1
You probably heard iOS 9.1 includes new emojis (Take a look at the changelog for all the details.) Updates include sports — volleyball and hockey; gestures — such as the middle finger and sign of the horns (“rock on”); as well as food items — such as a hot dog, taco, and more. The helper provided is also compatible with iOS 9.1!
Let’s approach the problem
A quick inspection on the iOS 9 runtime showed there was a class named UIKeyboardEmojiCategory which is the object responsible for managing Emoji in the current category.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/* Generated by RuntimeBrowser Image: /System/Library/Frameworks/UIKit.framework/UIKit */ @interface UIKeyboardEmojiCategory : NSObject { int _categoryType; NSString * _displaySymbol; NSArray * _emoji; int _lastVisibleFirstEmojiIndex; } @property int categoryType; @property (getter=displaySymbol, readonly) NSString *displaySymbol; @property (retain) NSArray *emoji; @property (nonatomic) int lastVisibleFirstEmojiIndex; @property (getter=name, nonatomic, readonly) NSString *name; + (id)categories; + (id)categoryForType:(int)arg1; + (id)computeEmojiFlagsSortedByLanguage; + (id)displayName:(int)arg1; + (id)emojiRecentsFromPreferences; + (BOOL)emojiString:(id)arg1 inGroup:(unsigned int*)arg2 withGroupCount:(int)arg3; + (id)flagEmojiCountryCodesCommon; + (id)flagEmojiCountryCodesReadyToUse; + (unsigned int)hasVariantsForEmoji:(id)arg1; + (id)loadPrecomputedEmojiFlagCategory; + (id)localizedStringForKey:(id)arg1; + (int)numberOfCategories; + (id)stringToRegionalIndicatorString:(id)arg1; - (int)categoryType; - (void)dealloc; - (id)displaySymbol; - (id)emoji; - (int)lastVisibleFirstEmojiIndex; - (id)name; - (void)releaseCategories; - (void)setCategoryType:(int)arg1; - (void)setEmoji:(id)arg1; - (void)setLastVisibleFirstEmojiIndex:(int)arg1; @end |
First we get a reference to every category available. We do this by creating a Class type using NSClassFromString. Using NSClassFromString we’re able to access classes which weren’t meant to be public in the first place. Next, we check if our reference to UIKeyboardEmojiCategory responds tonumberOfCategories and if it does, we loop over the number of categories and add the category to our categories array.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Let's get a reference to Apple's UIKeyboardEmojiCategory class Class UIKeyboardEmojiCategory = NSClassFromString(@"UIKeyboardEmojiCategory"); // Loop over all categories & save them in an array NSMutableArray *categories = [NSMutableArray array]; if ([UIKeyboardEmojiCategory respondsToSelector:@selector(numberOfCategories)]) { NSInteger numberOfCategories = [UIKeyboardEmojiCategory numberOfCategories]; for (NSUInteger i = 0; i < numberOfCategories; i++) { [categories addObject:[UIKeyboardEmojiCategory categoryForType:i]]; } } |
Building this code results into some compiler errors:
To fix this, we should make the compiler ‘know’ about these methods. We do this by creating a category on NSObject and copy over the method signatures from the UIKeyboardEmojiCategory header.
1 2 3 4 5 6 |
@interface NSObject (UIKeyboardEmojiCategory) + (NSInteger)numberOfCategories; + (id)categoryForType:(NSInteger)type; @end |
Next, we should loop over every category & get out the Emoji. But first let’s create some containing objects to store our information.
1 2 3 4 5 6 |
@interface MyEmojiCategory : NSObject @property (strong, nonatomic) NSString *name; @property (strong, nonatomic) NSArray<MyEmoji *> *emoji; @end |
1 2 3 4 5 6 |
@interface MyEmoji : NSObject @property (strong, nonatomic) NSString *emojiString; @property (strong, nonatomic) NSArray<NSString *> *variations; @end |
Next up, we’re going to loop over every category we got and get out thename and the displayName.
We’re going to ignore the ‘Recent’ category because we only want unique Emoji.
The Flags category is a bit special, more on that later.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// Loop over all categories for (id category in categories) { // Let's get thte category name NSString *categoryName = [category performSelector:@selector(name)]; // Ignore the 'recent' category, so we only get unique Emoji if ([categoryName hasSuffix:@"Recent"]) { continue; } // Get the display name for the current category NSString *displayName = [UIKeyboardEmojiCategory displayName:(int)[categories indexOfObject:category]]; // Instantiate our own category container MyEmojiCategory *myEmojiCategory = [MyEmojiCategory new]; myEmojiCategory.name = displayName; // The flags category is a bit special, we have to compute the flags and populate the array ourselves if ([displayName isEqualToString:@"Flags"]) { // TODO fix this category } // TODO add Emoji loop } |
Don’t forget to add the following method signature to your UIKeyboardEmojiCategory category.
1 |
+ (id)displayName:(int)arg1 |
Now, within this loop, we’ll add another loop for all the emoji.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// Let's create an array of Emoji for this category NSMutableArray *categoryEmoji = [NSMutableArray array]; NSMutableArray *emojiArray = [category valueForKey:@"emoji"]; for (id emoji in emojiArray) { // Emoji! NSString *emojiString = [emoji valueForKey:@"emojiString"]; // Create our own emoji container MyEmoji *myEmoji = [MyEmoji new]; myEmoji.emojiString = emojiString; // Let's create the variations container NSMutableArray<NSString *> *emojiVariations = [NSMutableArray new]; [emojiVariations addObject:emojiString]; // Variant mask indicates whether or not there are variations for a given emoji int vmask = [[emoji valueForKey:@"variantMask"] intValue]; // Fritzpatrick scale https://en.wikipedia.org/wiki/Fitzpatrick_scale // Diversity http://www.unicode.org/reports/tr51/index.html#Diversity if (vmask == 2 || vmask == 3) { // These codes are specified on the unicode consortium website [emojiVariations addObject:[NSString stringWithFormat:@"%@\U0001F3FB", emojiString]]; // 1-2 [emojiVariations addObject:[NSString stringWithFormat:@"%@\U0001F3FC", emojiString]]; // 3 [emojiVariations addObject:[NSString stringWithFormat:@"%@\U0001F3FD", emojiString]]; // 4 [emojiVariations addObject:[NSString stringWithFormat:@"%@\U0001F3FE", emojiString]]; // 5 [emojiVariations addObject:[NSString stringWithFormat:@"%@\U0001F3FF", emojiString]]; // 6 } // Set the variations myEmoji.variations = emojiVariations; // Add the emoji to the category [categoryEmoji addObject:myEmoji]; } // Set all the emoji on the category myEmojiCategory.emoji = categoryEmoji; |
A bit about diversity
In the code above you’ll notice we’re checking the variantMask of the Emoji, and we’ll be populating the emojiVariations array with 5 combinations. Where do these codes come from? If you take a look at the Emoji technical report at the diversity section, you’ll see that there are 5 modifiers available for skin tone. A colored emoji is basically simply a combination of 2 emoji:
So what we’re literally doing in the code above, is combine the base emoji with a colored swatch, which then results in a colored emoji.
What about the flags?
The flags are a special category of Emoji, which aren’t populated by default. We get all flag Emoji by calling computeEmojiFlagsSortedByLanguage on UIKeyboardEmojiCategory, which returns an array of Emoji.
Problem with this is that the Emoji array on every other category is an object with keys & values, so we create a dict, wrap the Emoji in it and set the Emoji property on the category.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// The flags category is a bit special, we have to compute the flags and populate the array ourselves if ([displayName isEqualToString:@"Flags"]) { // flagEmoji contains the emoji itself NSArray *flagEmoji = [UIKeyboardEmojiCategory computeEmojiFlagsSortedByLanguage]; NSMutableArray *arrFlagEmoji = [NSMutableArray new]; // Loop over every flag & wrap them in a dict so it conforms to the other categories for (NSString *flag in flagEmoji) { [arrFlagEmoji addObject:@{ @"emojiString": flag, @"variantMask": @0 }]; } // Set all the emoji on the flag category [category setEmoji:arrFlagEmoji]; } |
Don’t forget to add the method signatures:
1 2 |
- (void)setEmoji:(id)arg1; + (id)computeEmojiFlagsSortedByLanguage; |
Creating the demo
For the demo, I created a UITableViewController to display every Emoji (variations included) in its own section and with every variation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
@interface MyTableViewController () @property (strong, nonatomic) NSArray<MyEmojiCategory *> *emojiCategories; @end @implementation MyTableViewController - (void)viewDidLoad { [super viewDidLoad]; self.title = @"Emoji fiesta!"; [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"emojiCell"]; self.emojiCategories = [EmojiHelper getEmoji]; } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.emojiCategories count]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { MyEmojiCategory *category = [self.emojiCategories objectAtIndex:section]; return [category.emoji count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"emojiCell" forIndexPath:indexPath]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"emojiCell"]; } cell.textLabel.text = [self getEmojiStringAtIndexPath:indexPath]; return cell; } - (NSString *)getEmojiStringAtIndexPath:(NSIndexPath *)indexPath { MyEmojiCategory *emojiCategory = [self.emojiCategories objectAtIndex:indexPath.section]; MyEmoji *emoji = [emojiCategory.emoji objectAtIndex:indexPath.row]; return [emoji.variations componentsJoinedByString:@" "]; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { MyEmojiCategory *emojiCategory = [self.emojiCategories objectAtIndex:section]; return emojiCategory.name; } @end |
You can find the helper with the demo project on Github.