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 (0x610x7A) up to the Regional Indicator Symbol range (0x1F1E60x1F1FF)
  • 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!