Recortar textos con StringInfo en Xamarin Forms

¡Hola!
Antes que nada, si ya conoces la clase StringInfo seguramente te puedas ahorrar leer esta entrada 🙂 La cosa es que hoy estaba depurando un error en una aplicación Xamarin Forms (el problema no es exclusivo de XF) y me encontré con algo bastante curioso, al menos para mí. En la siguiente captura vamos a ver algo que representa más o menos lo que tenía:¿Qué vemos en la captura? Pues un texto con un emoji al final; a ese texto, en la siguiente línea, le vamos a recortar el último caracter y… como vemos en la imagen, al depurar e intentar visualizar el valor de subtext nos da un error que dice Debuggee returned error code INVALID_ARGUMENT.
Poniendo un poco de contexto al error que estaba tratando de corregir, el código lo que hacía era, calcular (más o menos) las líneas que ocupaba un texto y, si tenía más de x líneas, recortar el texto para que se mostrase un máximo de x líneas y se le añadiese un texto de Ver más. El problema venía porque se estaba dando una situación parecida a la que hemos visto en la captura. Vamos a ver si con la siguiente captura aclaramos un poco más lo que está ocurriendo:
Ahora está más claro, ¿no? Si nos fijamos, hemos cambiado el texto y ahora no aparece el emoticono, pero realmente lo único es que ahora se ve la representación de ese emoticono (en la ventana de Locals podemos apreciar que sí seguimos viendo el emoticono). Vamos a darle una vuelta más para que nos quede un poco más claro:
Como vemos en la captura anterior, si miramos el número de caracteres que representa nuestro emoticono son 2, y justamente ese es el problema. Cuando estábamos haciendo esto:

1
    textWithEmoji.Substring(0, textWithEmoji.Length - 1)

Lo que estábamos haciendo era «cortar a la mitad» nuestro emoji, y esto hacía que a la hora de que el depurado nos intente mostrar la representación de ese texto nos dé error (Debuggee returned error code INVALID_ARGUMENT), y, además en mi caso estaba haciendo que se lanzase una excepción en el código que calculaba el número de líneas… Y, ¿cómo lo podemos solucionar? Para mi objetivo (ir eliminando texto hasta que «entre» en x líneas), me encontré que la clase StringInfo sirve para calcular tamaños y recortar textos basándonos en «unidades», es decir, que nuestro emoji en vez de contar como dos caracteres (con el problema que ello nos conlleva cuando lo «cortamos por la mitad»), cuenta como una unidad.
Ahora, en vez de utilizar directamente string, utilizamos StringInfo, y nos valemos de LengthInTextElements para calcular el tamaño de nuestro texto en vez de Lenght, y SubstringByTextElements en vez de Substring. Con esto, como vemos en la captura anterior, nuestro emoticono cuenta como 1 elemento y al recortar el texto, como se recortan por elementos (unidades) no corremos el riesgo de cortar a la mitad nuestro emoticono.
El único problema que puedes encontrar es que si tu proyecto Xamarin Forms es PCL, no está disponible el método SubstringByTextElements, sólo tendrías acceso a la propiedad LengthInTextElements. Pero no hay problema, desde los proyectos nativos (iOS y Android) sí que los tenemos disponibles, por lo podríamos tener algo así en el proyecto PCL:

1
2
3
4
5
6
7
8
9
10
11
12
    public interface IStringInfoFactory
    {
        IStringInfo Create();
        IStringInfo Create(string value);
    }
 
    public interface IStringInfo
    {
        int LengthInTextElements { get; }
        string SubstringByTextElements(int startingTextElement);
        string SubstringByTextElements(int startingTextElement, int lengthInTextElements);
    }

y en cada uno de los proyectos nativos algo así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    [assembly: Dependency(typeof(Utils.StringInfoFactory))]
    namespace iOS.Utils
    {
        public class StringInfoFactory : IStringInfoFactory
        {
            public IStringInfo Create()
            {
                return new StringInfo();
            }
 
            public IStringInfo Create(string value)
            {
                return new StringInfo(value);
            }
        }
 
        class StringInfo : System.Globalization.StringInfo, IStringInfo
        {
            public StringInfo() : base() { }
            public StringInfo(string value) : base(value) { }
        }
    }

Y cuando queramos crear desde nuestro proyecto PCL un StringInfo, sólo tenemos que hacer algo así:

1
    var textWithEmoji = DependencyService.Get<IStringInfoFactory>().Create("Este texto tiene un emoji con una cara 😬");

Y con esto podríamos utilizar StringInfo en nuestros proyectos PCL.

¡Un saludo!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *