Swift Tricks: Emoji Flags
Toying around with some code on the bus this morning, I came across an interesting fact about region flag emoji. Among the thousands of emoji that the Unicode standard defines, 270 of them represent region flags, each corresponding to a two-character region code: “us” means 🇺🇸, for example.
My curiosity was this: is there a way to programmatically generate the flag
emoji 🇺🇸 from the string "us"
? I was afraid that, like many other emoji, the
flags would each have a unique name — something like REGIONAL FLAG UNITED STATES
— and would require a lookup table to translate between the basic string "us"
and the resulting emoji. Not so!
It turns out each flag is defined as a two-character sequence of Regional Indicator Symbols, each of which corresponds to a letter A-Z. When rendered by a sufficiently smart text engine, a valid pair of these symbols turns into the flag for the region named by the two-character code.
This meant that turning a two-character ASCII-alphabetic string into a flag emoji was an algorithmic affair, rather than one requiring a large lookup table. Roughly speaking, we’d want to:
- Take the input string, lowercase it, and make sure its two characters fall
between
'a'
and'z'
- Translate each character from the ASCII lowercase alphabet range
(
0x61
–0x7A
) up to the Regional Indicator Symbol range (0x1F1E6
–0x1F1FF
) - Return the two-character string containing both translated indicator symbols
This seemed easy enough to just try out, with a couple extra precautions about input thrown in. First I started with the translation bit:
func isLowercaseASCIIScalar(_ scalar: Unicode.Scalar) -> Bool {
return scalar.value >= 0x61 && scalar.value <= 0x7A
}
func regionalIndicatorSymbol(for scalar: Unicode.Scalar) -> Unicode.Scalar {
precondition(isLowercaseASCIIScalar(scalar))
// 0x1F1E6 marks the start of the Regional Indicator Symbol range and corresponds to 'A'
// 0x61 marks the start of the lowercase ASCII alphabet: 'a'
return Unicode.Scalar(scalar.value + (0x1F1E6 - 0x61))!
}
With the per-character work out of the way, we can provide a convenience function on String to perform a few extra checks and do the wholesale translation:
func emojiFlag(for countryCode: String) -> String! {
let lowercasedCode = countryCode.lowercased()
guard lowercasedCode.characters.count == 2 else { return nil }
guard lowercasedCode.unicodeScalars.reduce(true, { accum, scalar in accum && isLowercaseASCIIScalar(scalar) }) else { return nil }
let indicatorSymbols = lowercasedCode.unicodeScalars.map({ regionalIndicatorSymbol(for: $0) })
return String(indicatorSymbols.map({ Character($0) }))
}
Just like that, we’ve got a programmatic translation from "us"
to 🇺🇸! Let’s
try it out with a few extra cases:
emojiFlag(for: "de") // 🇩🇪
emojiFlag(for: "is") // 🇮🇸
We can even get crazy, and ask for the flag equivalent of every ISO region code
that Apple includes on Locale
:
Locale.isoRegionCodes.map({ emojiFlag(for: $0)! })
At the time of writing, this returns a 257-element array of flags. Funnily enough, Wikipedia claims there are only 256 “regular regions,” but two “macroregion sequences” — I wonder which of these Apple is including?
You can download all this code in playground form and test it out yourself. Enjoy your newfound multiregionalism!