👩👩👧👦 : Un symbole qui rend fou
\n\nImaginez : vous développez une application iOS, vous traitez du texte, vous comptez des caractères. Tout fonctionne parfaitement, jusqu'à ce que vous rencontriez l'emoji d'une famille de trois femmes et deux filles. Apparemment un seul symbole à l'écran, mais Swift dit que c'est... 4 symboles ? Ou 7 ? Peut-être 11 ? Une douleur familière ? Bienvenue dans le monde d'Unicode et des Strings Swift !
\n\nCette question est l'une des plus populaires sur Stack Overflow dans la section Swift. Et pour cause. La simplicité apparente des chaînes se transforme en véritable quête lorsque les emojis composites entrent en jeu. Dans cet article, nous n'expliquerons pas seulement pourquoi cela se produit, mais nous montrerons aussi comment travailler correctement avec de telles chaînes en Swift. Si vous codez en Swift et êtes confronté au traitement des entrées utilisateur, des emojis ou de l'internationalisation, ce contenu est pour vous.
\n\n🔍 Le problème : "Combien de caractères dans cette chaîne ?"
\n\nAllons droit au but. Ouvrez Xcode Playground et écrivez :
\n\nimport Foundation\n\nlet familyEmoji = "👩👩👧👦"\nprint(familyEmoji.count) // 1 ? 4 ? 7 ?\n\n\nSi vous attendiez 1, vous avez raison du point de vue de l'utilisateur. Si vous attendiez 4 (quatre figures) — c'est aussi logique. Et Swift affichera 1. Mais essayez familyEmoji.unicodeScalars.count — et vous obtiendrez 7 ! Et si vous décomposez avec utf16.count — ce sera 11.
Où est le piège ? L'emoji 👩👩👧👦 est un cluster graphémique composite. Il est composé de plusieurs scalaires Unicode, unis par des caractères de liaison invisibles (ZWJ — Zero Width Joiner). Swift traite ces séquences comme un seul symbole visible (un graphème), ce qui est correct du point de vue de l'utilisateur, mais déroute lorsque vous essayez de travailler avec les "entrailles" de la chaîne.
\n\n🧱 Anatomie d'un emoji : scalaires, clusters et ZWJ
\n\nPour comprendre la magie, décomposons notre emoji en ses éléments constitutifs. Chaque caractère Unicode est un scalaire (code numérique). Pour 👩👩👧👦, la séquence ressemble à ceci :
\n\nlet scalars = familyEmoji.unicodeScalars\nfor scalar in scalars {\n print(scalar.value, scalar.properties.name ?? "unknown")\n}\n// Sortie (simplifiée) :\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\nVous voyez ? Quatre figures (deux femmes, une fille, un garçon) sont reliées par trois caractères ZWJ invisibles (code 8205). Swift, suivant la norme Unicode, interprète toute cette séquence comme un seul cluster graphémique étendu. C'est pourquoi .count renvoie 1 — c'est le nombre de "caractères utilisateur".
🔄 Comparaison des approches : count vs unicodeScalars vs utf16
\n\nSystématisons. Pour la chaîne "👩👩👧👦", nous obtenons trois réponses différentes selon l'API utilisée :
- \n
.count(clusters graphémiques) —1. Correct pour l'interface utilisateur, l'affichage dans UILabel. \n .unicodeScalars.count(scalaires Unicode) —7. Utile pour le traitement de texte de bas niveau. \n .utf16.count(unités de code UTF-16) —11. Utilisé pour la compatibilité avec Objective-C NSString. \n .utf8.count(octets UTF-8) —25. Pour le travail avec le réseau et les fichiers. \n
Chacune de ces approches résout un problème spécifique. L'erreur du débutant est d'utiliser .count pour limiter la longueur de saisie dans un champ de texte (par exemple, "pas plus de 140 caractères"). Pour l'utilisateur, "👩👩👧👦" est un seul caractère, et une limite de 140 tels emojis est normale. Mais si vous stockez la chaîne dans une base de données avec une longueur fixe en octets — vous avez besoin de .utf8.count.