👩👩👧👦: Un solo carácter que vuelve loco
\n\nImagina: estás escribiendo una aplicación para iOS, procesando texto, contando caracteres. Todo funciona perfectamente, hasta que aparece el emoji de una familia de tres mujeres y dos niñas. En la pantalla parece un solo carácter, pero Swift dice que son... ¿4 caracteres? ¿O 7? ¿Quizás 11? ¿Te suena familiar? ¡Bienvenido al mundo de Unicode y las cadenas de Swift!
\n\nEsta pregunta es una de las más populares en Stack Overflow en la sección de Swift. Y con razón. La aparente simplicidad de las cadenas se convierte en una verdadera odisea cuando entran en juego los emojis compuestos. En este artículo no solo explicaremos por qué sucede esto, sino que también mostraremos cómo trabajar correctamente con estas cadenas en Swift. Si programas en Swift y te enfrentas al procesamiento de entrada de usuario, emojis o internacionalización, este contenido es para ti.
\n\n🔍 El problema: "¿Cuántos caracteres tiene esta cadena?"
\n\nVamos al grano. Abre Xcode Playground y escribe:
\n\nimport Foundation\n\nlet familyEmoji = "👩👩👧👦"\nprint(familyEmoji.count) // 1? 4? 7?\n\n\nSi esperabas 1, tienes razón desde la perspectiva del usuario. Si esperabas 4 (cuatro figuras), también es lógico. Y Swift devolverá 1. Pero prueba familyEmoji.unicodeScalars.count — ¡y obtendrás 7! Y si lo descompones con .utf16.count — será 11.
¿Cuál es el truco? El emoji 👩👩👧👦 es un clúster de grafemas compuesto. Está formado por varios escalares Unicode, unidos por caracteres invisibles de unión (ZWJ — Zero Width Joiner). Swift trata estas secuencias como un solo carácter visible (un grafema), lo cual es correcto desde la perspectiva del usuario, pero resulta confuso cuando intentas trabajar con las "entrañas" de la cadena.
\n\n🧱 Anatomía de un emoji: escalares, clústeres y ZWJ
\n\nPara entender la magia, descompongamos nuestro emoji en sus partes. Cada carácter Unicode es un escalar (un código numérico). Para 👩👩👧👦, la secuencia es la siguiente:
\n\nlet scalars = familyEmoji.unicodeScalars\nfor scalar in scalars {\n print(scalar.value, scalar.properties.name ?? "unknown")\n}\n// Salida (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\n¿Ves? Cuatro figuras (dos mujeres, una niña, un niño) unidas por tres caracteres ZWJ invisibles (código 8205). Swift, siguiendo el estándar Unicode, interpreta toda esta secuencia como un único clúster de grafemas extendido. Por eso .count devuelve 1: es la cantidad de "caracteres de usuario".
🔄 Comparación de enfoques: count vs unicodeScalars vs utf16
\n\nOrganicemos la información. Para la cadena "👩👩👧👦" obtenemos tres respuestas diferentes según la API que usemos:
- \n
.count(clústeres de grafemas) —1. Correcto para la interfaz de usuario, para mostrar en UILabel. \n .unicodeScalars.count(escalares Unicode) —7. Útil para procesamiento de texto de bajo nivel. \n .utf16.count(unidades de código UTF-16) —11. Se usa para compatibilidad con Objective-C NSString. \n .utf8.count(bytes UTF-8) —25. Para trabajar con redes y archivos. \n
Cada uno de estos enfoques resuelve un problema específico. El error típico de un principiante es usar .count para limitar la longitud de entrada en un campo de texto (por ejemplo, "no más de 140 caracteres"). Para el usuario, "👩👩👧👦" es un solo carácter, y un límite de 140 emojis como este es normal. Pero si almacenas la cadena en una base de datos con una longitud fija en bytes, necesitarás .utf8.count.