👩👩👧👦: Um símbolo que enlouquece
\n\nImagine: você está escrevendo um aplicativo para iOS, processando texto, contando caracteres. Tudo funciona perfeitamente, até encontrar o emoji de família com três mulheres e duas meninas. Aparentemente um símbolo na tela, mas o Swift diz que são... 4 caracteres? Ou 7? Ou talvez 11? Dor familiar? Bem-vindo ao mundo do Unicode e das Strings Swift!
\n\nEsta pergunta é uma das mais populares no Stack Overflow na seção Swift. E não é à toa. A aparente simplicidade das strings se transforma em uma verdadeira aventura quando emojis compostos entram em jogo. Neste artigo, não apenas explicaremos por que isso acontece, mas também mostraremos como trabalhar corretamente com essas strings no Swift. Se você programa em Swift e lida com processamento de entrada do usuário, emojis ou internacionalização — este material é para você.
\n\n🔍 O problema: "Quantos caracteres há nesta string?"
\n\nVamos direto ao ponto. Abra o Xcode Playground e escreva:
\n\nimport Foundation\n\nlet familyEmoji = "👩👩👧👦"\nprint(familyEmoji.count) // 1? 4? 7?\n\n\nSe você esperava 1, está certo do ponto de vista do usuário. Se esperava 4 (quatro figuras) — também é lógico. E o Swift retornará 1. Mas tente familyEmoji.unicodeScalars.count — e obterá 7! E se analisar com utf16.count — será 11.
Qual é o truque? O emoji 👩👩👧👦 é um cluster de grafemas composto. Ele consiste em vários escalares Unicode, unidos por caracteres invisíveis de junção (ZWJ — Zero Width Joiner). O Swift conta essas sequências como um único caractere visível (grafema), o que é correto do ponto de vista do usuário, mas confunde quando você tenta trabalhar com os "internos" da string.
\n\n🧱 Anatomia do emoji: escalares, clusters e ZWJ
\n\nPara entender a mágica, vamos decompor nosso emoji em suas partes. Cada caractere Unicode é um escalar (código numérico). Para 👩👩👧👦, a sequência é assim:
\n\nlet scalars = familyEmoji.unicodeScalars\nfor scalar in scalars {\n print(scalar.value, scalar.properties.name ?? "unknown")\n}\n// Saída (simplificada):\n// 128105 (WOMAN)\n// 8205 (ZERO WIDTH JOINER)\n// 128105 (WOMAN)\n// 8205 (ZERO WIDTH JOINER)\n// 128103 (GIRL)\n// 8205 (ZERO WIDTH JOINER)\n// 128102 (BOY)\n\n\nViu? Quatro figuras (duas mulheres, uma menina, um menino) unidas por três caracteres ZWJ invisíveis (código 8205). O Swift, seguindo o padrão Unicode, interpreta toda essa sequência como um único cluster de grafema estendido. É por isso que .count retorna 1 — é a quantidade de "caracteres do usuário".
🔄 Comparação de abordagens: count vs unicodeScalars vs utf16
\n\nVamos sistematizar. Para a string "👩👩👧👦", obtemos três respostas diferentes dependendo da API que usamos:
- \n
.count(clusters de grafemas) —1. Correto para UI, exibição em UILabel. \n .unicodeScalars.count(escalares Unicode) —7. Útil para processamento de texto de baixo nível. \n .utf16.count(unidades de código UTF-16) —11. Usado para compatibilidade com Objective-C NSString. \n .utf8.count(bytes UTF-8) —25. Para trabalho com rede e arquivos. \n
Cada uma dessas abordagens resolve um problema específico. O erro do iniciante é usar .count para limitar o comprimento da entrada em um campo de texto (por exemplo, "no máximo 140 caracteres"). Para o usuário, "👩👩👧👦" é um caractere, e um limite de 140 desses emojis é normal. Mas se você armazena a string em um banco de dados com comprimento fixo em bytes — você precisa de .utf8.count.