Precios accesibles, nuestro aprendizaje desde la perspectiva iOS

Cómo mejoramos la accesibilidad de nuestro componente de precio, y cómo nos marcó el camino hacia nuevos saberes para nuestro sistema de diseño.

Ana Cristina Calderón Castrillon
Mercado Libre Tech
11 min readFeb 1, 2023

--

Por Ana Calderon y Laura Sarmiento

Ultima actualización: 23 de Octubre 2024

Leer esta historia en inglés.

“Una app iOS accesible digitalmente es aquella cuyas funcionalidades pueden ser utilizadas por todas las personas de forma natural y amigable, con independencia de sus capacidades físicas o cognitivas. Así, cualquier persona usuaria podrá acceder al contenido existente en la aplicación sin ningún obstáculo.”

En Mercado Libre, transformamos la vida de millones de personas en América Latina democratizando el comercio y los servicios financieros; para alcanzar esto debemos lograr que todas las personas puedan estar incluidas, ya que democratizar implica la posibilidad de que todas las personas participen sin importar sus condiciones. Por ese motivo ser accesibles está implícito en nuestra misión y nos lleva a enfocarnos en la construcción de apps accesibles para que todas las personas usuarias puedan utilizarlas de manera fácil e intuitiva, como nos cuenta Guille Paz en su post “Qué aprendimos al trabajar en accesibilidad digital” .

Anteriormente Martín Di Luzio compartió su artículo “Aprendizajes al desarrollar precios accesibles en Mercado Libre” desde el stack Web, posteriormente Juan Ignacio Unzurrunzaga desde el stack Android presentó su perspectiva en el post “Precios accesibles, nuestro aprendizaje desde la perspectiva Android” y para cerrar esta serie de historias, queremos compartir nuestros aprendizajes desde el stack iOS.

Si aún no leíste esas historias, desde el equipo de Design System te contamos qué es la accesibilidad digital:

“La accesibilidad digital es la práctica inclusiva que busca garantizar el libre acceso a la web y a las aplicaciones nativas para todas las personas, a través de un diseño que permita percibir, entender, navegar e interactuar libremente con el contenido.”

Introducción al componente Money Amount

Como parte de nuestra cultura, tenemos como premisa la accesibilidad digital, es decir, que todos los equipos de desarrollo de producto piensen, diseñen y desarrollen con ese objetivo en mente. Para agilizar esto, desde Design System, ofrecemos a nuestros desarrolladores el componente para mostrar precios llamado Money Amount, que se refiere al valor de un producto o a cualquier monto de dinero.

El componente Money Amount puede mostrar precios o una moneda específica en la app con 3 variaciones:

  1. precio
  2. descuento
  3. precio con descuento

Precio

Como ya hemos mencionado anteriormente, este componente se usa para referir al valor de un ítem o a una cantidad específica de dinero.

La anatomía del componente incluye:

  1. un valor,
  2. los decimales,
  3. la moneda o divisa en la que está expresado el valor, lo cuál además determina el separador decimal y la cantidad de decimales a mostrar.
Imagen que representa la anatomía del componente Money Amount, por ejemplo $32.999,99 , siendo el signo pesos la divisa en la que se expresa, seguido del número 32.999, que es el número entero del monto del dinero, y finalmente el número 99 que representa a los decimales que se utilizan para representar los centavos y que en este caso se muestra como superíndice.

Nuestra variante simple cuenta con 3 tipos de variaciones:

  • Positive: Valor/cantidad mayor a cero:
Ejemplo del componente Money Amount con Type = Positive; se muestra el monto $1000.
  • Negative: Valor/cantidad menor a cero:
Ejemplo del componente Money Amount con Type = Negative; se muestra el monto $1000 con el signo menos precediendo a éste.
  • Previous: Valor/cantidad anterior al actual, se utiliza en el Money Amount combo del cual hablaremos más adelante:
Ejemplo del componente Money Amount con Type = Previous. Se muestra el monto $1000 tachado.

También contamos con un sufijo opcional, el cual permite brindarle un contexto al monto:

Ejemplo del componente Money Amount con el sufijo “unidad”

Descuento

Se utiliza para referirse al valor de descuento de un artículo.

Ejemplo del componente Money Amount Discount; se agrega la palabra en inglés “OFF”

Precio con Descuento

Utilizado para referirse al valor de un artículo o a una cantidad específica de dinero con un precio previo y/o un valor de descuento:

Ejemplo del componente Money Amount Combo, donde se especifica el precio anterior, el valor actual y el porcentaje de descuento.

Herramientas de Accesibilidad en iOS

Antes de comenzar con el código, es importante conocer algunas herramientas que usamos para testear y garantizar la calidad de la accesibilidad en nuestros componentes.

VoiceOver

Es la herramienta de lectura de pantalla que le ofrece Apple a todas las personas que usan iPhone o iPad, y brinda a los usuarios control sobre sus dispositivos sin tener que ver la pantalla. VoiceOver actúa como intermediario entre la interfaz de usuario de una aplicación y las acciones de las personas como tocar la pantalla o hacer gestos, proporcionando descripciones audibles sobre lo que se puede hacer en cada pantalla de la aplicación y lo que se está visualizando.

Para conocer cómo puedes activar esta herramienta en tu dispositivo y cómo hacer uso de él, te recomendamos leer esta información.

Apple nos proporciona un API de Accesibilidad para UIKit y SwiftUI, que nos permite ponerle etiquetas de accesibilidad a nuestras vistas. Estas son muy importantes ya que proporcionan el texto que lee VoiceOver; más adelante te explicamos cómo hacerlo.

Accessibility Inspector en Xcode

Cuando realizamos pruebas en el simulador, no tenemos acceso a VoiceOver ni a otras tecnologías de asistencia en el dispositivo (bueno, en realidad sí; en otra historia tal vez te contemos cómo logramos esto). Sin embargo, podemos testear la accesibilidad de una aplicación usando este Inspector que nos permite simular la voz en off, e identificar lo que experimentaría un usuario de VoiceOver.

Si deseas conocer más sobre este tema, te recomendamos ver este video.

Accesibilidad en iOS:

En cada componente de UI, Apple nos brinda una experiencia accesible por defecto, que puede ser aceptable o no dependiendo en gran medida del diseño del layout completo de la vista. En muchos casos, es necesario modificar el comportamiento por defecto para mejorar la UX; más adelante te explicaremos cómo lograrlo.

Money amount es uno de los componentes más usados para el manejo de precios y valores en nuestras apps de Mercado Libre y Mercado Pago. Por eso nos focalizamos mucho en él y lo tomamos como ejemplo el día de hoy.

El papel de la verbalización regional y la configuración numérica en la experiencia de VoiceOver

¿Es necesaria otra configuración? ¿Nos tenemos que preocupar por algo más?

Cada país tiene su propia configuración regional, por ejemplo, en algunos países el punto (.) es el separador de miles y la coma (,) separador de centavos, en otros países se usa diferente, la coma (,) es separador de miles y el punto (.) separador de centavos.

A continuación podrás identificar la configuración regional según el país:

  • Colombia
  • Venezuela
  • Argentina
  • Chile
  • España
  • Uruguay
  • Brasil
Ejemplo de formato regional con punto como separador de miles y como para decimales
  • México
  • EEUU
  • Canadá
Ejemplo de formato regional con coma como separador de miles y punto para decimales

¿Cómo identificar nuestra configuración? Consulta la guia de apple aquí.

Opciones para configurar el formato numérico

En versiones de iOS 16.0 en adelante permite cambiar el tipo de formato pero en versiones anteriores no es posible ya que viene por defecto predeterminado de la región.

Eso no es todo. Voice Over y sus configuraciones nos permiten algo más

El lector de pantalla permite definir una voz según el país, cuenta con varias opciones de regionalismo y un mismo idioma, al tener un voz con diferente región puede realizar una verbalización diferente a la esperada.

¿Quieres cambiar la voz de VoiceOver? Consulta la guia de apple aquí.

Ejemplos de algunas voces disponibles de VoiceOver

Algo importante para tener en cuenta es que en versiones iOS 15.7 e inferiores solo tiene disponibles dos versiones de español [España y México].

¿Cómo podemos asegurar una verbalización correcta en nuestras apps para iPhone?

  • Tener configurado el formato del país según la región, ubicación e idioma donde me encuentro.
  • Tener configurado la voz de Voice Over en el país donde me encuentro.

Con lo mencionado anteriormente, obtuvimos la experiencia que buscábamos:

$899.900 => Ochocientos noventa y nueve mil novecientos pesos.

$195.401 => Ciento noventa y cinco mil cuatrocientos uno pesos.

$1.299.900 => Un millón doscientos noventa y nueve mil novecientos pesos.

Errores detectados en la accesibilidad de Money Amount

Con las herramientas antes mencionadas, con el apoyo de nuestro equipo de accesibilidad y con personas usuarias expertas en la usabilidad de lectores de pantalla, logramos detectar que nuestro componente de Money Amount tenía muchas oportunidades para realmente ayudar a democratizar el comercio y los servicios financieros. Algunos de los errores detectados en estos ensayos son:

  • El lector no anuncia de forma correcta la divisa. Como ejemplo, para un precio con el signo “$” con el cual se desea representar “pesos”, el lector interpreta y anuncia la palabra “dólares”.
  • Sabemos que el precio tachado se interpreta como un precio anterior. Con la experiencia accesible por defecto que nos provee Apple, VoiceOver solo puede transmitir un valor omitiendo el estilo del texto. Esto impide que la persona usuaria conozca el estado de esta etiqueta, que apunta a indicar el valor sobre el cual se aplica el porcentaje de descuento.
  • En el caso del precio con descuento, el lector no lo detecta como un solo elemento compuesto. Si lo miramos como una sola cosa, o si pensamos cómo nos gustaría que alguien nos explique un precio, nos gustaría que nos digan el precio anterior, el precio actual y cuál es el descuento. ¡Pero no funciona así! Voiceover no reconoce tal cosa, por lo que lee los precios y el descuento individualmente fuera de contexto.
  • Al leer los separadores decimales y de miles, el lector de pantalla puede anunciar un precio de “nueve mil” (9.000) como “nueve punto cero, cero, cero”.

Ajustando la accesibilidad de nuestro componente precio

La accesibilidad de nuestros componentes se enfoca en utilizar las herramientas que nos ofrece UIKit. Por lo general esto se consigue utilizando las propiedades básicas de UIAccessibility.

La lógica que usamos para definir la accesibilidad de un componente de UI es:

Diagrama que representa el patrón de diseño aplicado a la accesibilidad de nuestros componentes UI, donde se explica nuestro protocolo accessibility manager y su aplicación en los custom views.

Tenemos un protocolo, una clase y una vista:

Protocolo AccessibilityManager

Dado que todos nuestros componentes UI deben ser accesibles, diseñamos este protocolo que nos permite agilizar el proceso de implementación de accesibilidad, además de tener un código más legible.

Contiene los siguientes métodos:

  • viewUpdate:

Es el método en donde se espera que se modifiquen los atributos de accesibilidad del componente por parte de cada equipo que diseña y desarrolla una experiencia de accesibilidad.

Aquí podemos modificar las propiedades de UIAccessibility como isAccessibilityElement y accessibilityLabel, entre otras. Este método se llama cada vez que es necesario modificar estas propiedades; por ejemplo, en Money Amount, si el desarrollador modifica el tipo de moneda de USD a COP, se debe modificar el accessibilityLabel del componente y esto se hace dentro de este método. Por ende, cada vez que cambia este atributo currency, llamamos al AccessibilityManager.viewUpdate dentro del didSet.

  • accessibilityActivated:

UIKit nos provee el protocolo UIAccessibilityAction, que tiene el método accessibilityActivated(). Este método se puede utilizar para hacer que los controles complejos sean más fácilmente accesibles para los usuarios.

La idea con nuestro accessibilityActivated es que podamos implementar allí las tareas que se ejecutarán cuando el método accessibilityActivated del protocolo UIAccessibilityAction se active y llamarla dentro de él.

Para el caso del componente Money Amount, no fue necesaria su implementación.

  • makeAnnouncement:

Este método tiene una implementación por defecto y se hace uso del mismo si en alguna parte de la implementación se requiere hacer un anuncio de las notificaciones que nos ofrece UIKit para el manejo de la accesibilidad.

Class AccessibilityManager

Esta clase es la encargada de implementar el protocolo AccessibilityManager.

Para el caso de cada variación (Precio, Descuento, Precio con Descuento), fue necesario realizar una clase para cada una, con la implementación del protocolo AccessibilityManager con el fin de separar la lógica de desarrollo por el principio de Responsabilidad única. Éste nos indica que una clase solo debe tener una responsabilidad, lo que nos permite hacer tests de nuestro código de forma más amena.

Precio:

En la línea nº 16 del siguiente bloque de código, se actualiza el accessibilityLabel de nuestra variación, indicando el texto que leerá el lector de pantalla cuando se enfoque en el componente.

En la línea nº 21 definimos la función privada createAccessibilityLabel donde implementamos la lógica y dependiendo del tipo (positive, negative, previous) o si posee un sufijo, se retorna el accessibilityLabel correspondiente:

Descuento:

En la línea de código nº 16, asignamos la propiedad accessibilityLabel de nuestra custom view, que concatena el valor de descuento seguido del texto “Off”; éste varía dependiendo del idioma configurado en la app.

Precio con Descuento:

A continuación, en la línea de código nº 16, modificamos la propiedad accessibilityLabel de nuestro View.

El método privado createAccessibilityLabel refleja el texto que va a leer el lector de pantalla, es decir la concatenación del precio anterior, el precio actual y el descuento.

View

Es nuestro Custom View, que representa al componente de UI, en este caso Money Amount. Esta vista tiene una instancia de la Clase AccessibilityManager que nos permite acceder a su comportamiento, como lo podemos observar en la línea nº 6 del siguiente bloque de código:

Finalmente les compartimos el resultado

Video de ejemplo con la implementación actual del componente de precios, y la experiencia resultante al navegarlo con VoiceOver
  • “Antes: 6500 pesos, ahora: 5249 pesos, 19 por ciento de descuento”
  • “Before: 6500 pesos, now: 5249 pesos, 19 percent off”

Actualización 23 de Junio de 2023:

Problema con verbalización de los números y cómo lo solucionamos

Nos encontramos con un problema en el componente de MoneyAmount, cuando se utilizaba con una cantidad mayor de 5 dígitos (ej 100.000), el VoiceOver anunciaba cada dígito por separado y no como número completo, pero cuando se usaba con 5 dígitos o menos los anunciaba correctamente (ej 20.000, 5.000, 30).

Solución:

El componente estaba recibiendo los números como un texto (String), por lo tanto el VoiceOver leía los dígitos separados y no como un todo. Teniendo en cuenta esto, se hicieron las modificaciones correspondientes para que el número que recibe esté con el formato DecimalStyle y que Voiceover lo lea correctamente.

El fix fue agregar dos nuevas propiedades (numberValue y numberFormatted), las cuales modifican el valor recibido en MoneyAmount como un NSNumber y cambia su formato para que sea con estilo decimal. numberFormatted se utiliza en el accessibilityLabel para que voiceOver la lea con el formato establecido. A continuación se muestra el código antes y después del fix.

Antes:

let integerValue = values.first?.replacingOccurrences(of: currencyInfo.thousandSeparator, with: "") ?? "0"

accesibilityLabel += "\(integerValue) \(integerValue == "1" ? currencyInfo.currencyName.localized() : currencyInfo.currencyNamePlural.localized()) "

Con fix:

let integerValue = values.first?.replacingOccurrences(of: currencyInfo.thousandSeparator, with: "") ?? "0"
let numberValue = NSNumber(value: Int(integerValue) ?? 0)
let numberFormatted = NumberFormatter.localizedString(from: numberValue, number: .decimal)

accesibilityLabel += "\(numberFormatted) \(integerValue == "1" ? currencyInfo.currencyName.localized() : currencyInfo.currencyNamePlural.localized()) "

Conclusión

En Mercado Libre valoramos la experiencia que vivimos todas las personas cuando usamos los productos del ecosistema Meli. UIKit nos ofrece una amplia variedad de herramientas para mejorar la accesibilidad de nuestros componentes y la experiencia de usuario. Como pueden ver, la accesibilidad es un factor muy importante en nuestras apps ya que deseamos que todas las personas usuarias puedan hacer uso de ellas y tener una experiencia agradable, independientemente de su entorno.

--

--