Accessible prices on iOS within Mercado Libre

How we improved the accessibility for our price component, and how it showed us the way towards new knowledge for our design system.

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

--

By Ana Calderon, Laura Sarmiento

Last updated: June 23, 2023

Read this story in spanish.

“A digitally accessible iOS app is the one whose functionalities can be used by all people in a natural and friendly way, regardless of their physical or cognitive abilities. Thus, any user can access the existing content in the application without any obstacles.”

At Mercado Libre, we transform the lives of millions of people in Latin America by democratizing trade and financial services. In order to achieve this, we must ensure that all people are included since democratization implies that everyone can participate regardless of their condition. This is why being accessible is implicit in our mission and leads us to focus on building accessible apps so users can operate them easily and intuitively, as Guille Paz tells us in his post “What we learned while working in digital accessibility”.

Previously, Martín Di Luzio shared his article Lessons learned when developing accessibility in prices at Mercado Libre from the Web stack. Later, Juan Ignacio Unzurrunzaga from the Android stack, presented his perspective in the post “Affordable prices, our learning from the Android perspective”. To close this series of stories, we would like to share learnings from the iOS stack.

In case you haven’t read those stories yet, from the Design System team, we would like to share what digital accessibility is:

“Digital accessibility is the inclusive practice that seeks to guarantee free access to the web and native applications for all people, through a design that allows them to perceive, understand, browse and interact freely with the content.”

Introduction to the Money Amount component

Our culture has digital accessibility as a premise, which means that all product development teams think, design and develop with that objective in mind. To expedite that, on the Design System team, we offer our developers the component to display prices called Money Amount, which refers to the value of a product or to any amount of money.

The Money Amount component can display prices or a specific currency in the app in three different ways:

  1. price
  2. discount
  3. price with discount

Price

As we have already mentioned, this component is used to refer to the value of an item or to a specific amount of money.

The component anatomy includes:

  1. a value;
  2. the decimals;
  3. the currency in which it is expressed, which also determines the decimal separator and the number of decimal places to display.
Image representing the anatomy of the Money Amount component, for example $32,999.99, the peso sign being the currency in which it is expressed, followed by the number 32,999, which is the whole number of the amount of money, and finally number 99 that are the decimals used to represent cents, shown in this case as superscript.

Our simple variant has 3 types of variations:

Positive: Value/amount greater than zero:

Example of the Money Amount component with Type = Positive; the amount $1000 is displayed.

Negative: Value/amount less than zero:

Example of the Money Amount component with Type = Negative; the amount $1000 is shown with the minus sign preceding it.

Previous: Value/amount prior to the current one; it is used in the Money Amount combo we will refer to later:

Example of the Money Amount component with Type = Previous; it shows the amount $1000 crossed out.

We also have an optional suffix, which allows us to contextualize the amount:

Example of the Money Amount component with the suffix “unit”.

Discount

It is used to refer to the discount value of an item:

Example of the Money Amount Discount component, the word “OFF” is added.

Price with Discount

Used to refer to the value of an item or to a specific amount of money with a prior price and/or discount value:

Example of the Money Amount Combo component, specifying the previous price, the current value and the discount percentage.

iOS Accessibility Tools

Before starting with the code, it’s important to know some tools that we use to test and ensure the quality of accessibility in our components.

VoiceOver

It is the screen-reading tool that Apple offers to those using iPhone or iPad, and gives users control over their devices without having to look at the screen. VoiceOver acts as an intermediary between an app’s user interface and people’s actions such as tapping or gesturing, providing audible descriptions of what can be done on each screen of the app and what is being displayed.

To find out how to activate this tool on your device and how to use it, we recommend you read this information.

Apple provides us with an Accessibility API for UIKit and SwiftUI, which allows us to place accessibility tags on our views. These are very important since they provide the text that VoiceOver reads — later we will explain how to go about it.

Accessibility Inspector in Xcode

When testing in the simulator, we don’t have access to VoiceOver or other assistive technologies on the device (well, actually we do; in another story we may tell you how we do this). However, we can test an app’s accessibility using this Inspector, which allows us to simulate the voice and identify what a VoiceOver user would experience.

If you wish to know more about this topic, we recommend you watch this video.

Accessibility in iOS

In each UI component, Apple provides us with an accessible default experience which may or may not be acceptable, depending on the design of the full view layout. In many cases, it is necessary to modify the default behavior to improve UX — later we will explain how to achieve it.

Money Amount is one of the most widely used components for managing prices and values ​​in our Mercado Libre and Mercado Pago apps. This is why we place great focus on it and take it as an example today.

Errors detected in the accessibility of Money Amount

With the aforementioned tools, with the support of our accessibility team and with expert users in the usability of screen readers, we were able to detect that our Money Amount component had vast opportunities to really help to democratize trade and financial services. Some of the errors detected are the following:

  • The reader does not announce the currency correctly. As an example, for a price with the sign “$” whose aim is to represent “pesos”, the reader interprets and announces the word “dollars”.
  • We know the strikethrough price is interpreted as a previous price. With the default accessible experience provided by Apple, VoiceOver can only convey a value omitting the text style. This prevents the user from knowing the status of this label, which aims to indicate the value on which the discount percentage was applied.
  • In the case of discounted price, the reader does not detect it as a single compound element. If we look at it as a single thing, or if we think how we would like someone to explain a price to us, we would like them to tell us the previous price, the current price and what the discount is. But it doesn’t work like that! Voiceover doesn’t recognize such a thing, so it reads the prices and discounts individually and out of context.
  • While reading the decimal and thousands separators, the screen reader may announce a price of “twelve thousand” (9,000) as “nine point zero, zero, zero”.

Adjusting the accessibility of our price component

The accessibility of our components focuses on using the tools that UIKit offers us. This is usually achieved using the basic properties of UIAccessibility.

The logic we use to define the accessibility of a UI component is:

Diagram that represents the design pattern applied to the accessibility of our UI components, explaining our accessibility manager protocol and its application in custom views.

We have a protocol, a class and a view:

AccessibilityManager Protocol

Since all of our UI components must be accessible, we’ve designed this protocol to allow us to streamline the accessibility implementation process, as well as to have more readable code.

It contains the following methods:

  • viewUpdate:

This is the method where the accessibility attributes of the component are expected to be changed by each team that designs and develops an accessibility experience.

Here we can modify UIAccessibility properties such as isAccessibilityElement and accessibilityLabel, among others. This method is called whenever it is necessary to modify these properties; for instance, in Money Amount, if the developer changes the type of currency from USD to COP, the accessibilityLabel of the component must be modified and this is done within this method. Thus, whenever this currency attribute changes, we call the AccessibilityManager.viewUpdate inside the didSet.

  • accessibilityActivated:

UIKit provides us with the UIAccessibilityAction, which has accessibilityActivated(). This method can be applied to make complex controls more easily accessible to users.

The idea with our accessibilityActivated is that there, we can implement the tasks that will be executed when the accessibilityActivated protocol UIAccessibilityAction is activated and then call it from within.

In the case of the Money Amount component, its implementation was not necessary.

  • makeAnnouncement:

This method has a default implementation and it is used if in any part of the implementation it is required to make an announcement of the notifications that UIKit offers us for accessibility management.

Class AccessibilityManager

This class is in charge of implementing the AccessibilityManager.

In the case of each variation (Price, Discount, Price with Discount), it was necessary to create a class for each, with the implementation of the AccessibilityManager protocol in order to separate the development logic by the principle of Single Responsibility. This principle indicates that a class should only have one responsibility, which allows us to test our code more effortlessly.

Price:

In line #16 of the following code block, the accessibilityLabel of our variation is updated, indicating the text that the screen reader will read when it focuses on the component.

In line #21, we define the private function createAccessibilityLabel where we implement the logic and, depending on the type (positive, negative, previous) and on whether it has a suffix, the corresponding accessibilityLabel is resumed:

Discount:

In code line #16, we assign the property accessibilityLabel of our custom view, which concatenates the discount value followed by the text “Off”; this varies depending on the language configured in the app.

Price with Discount:

Next, in line #16 of code, we modify the accessibilityLabel of our View.

The private method createAccessibilityLabel reflects the text that the screen reader will read, which is the concatenation of the previous price, the current price and the discount.

View

It is our Custom View, which represents the UI component — in this case Money Amount. This View has an AccessibilityManager Class instance that allows us to access its behavior, as we can see in line #6 of the following code block:

Finally, we’d like to share the result:

Example video with the current implementation of the price component, and the resulting experience when browsing it with VoiceOver.
  • “Antes: 6500 pesos, ahora: 5249 pesos, 19 por ciento de descuento”
  • “Before: 6500 pesos, now: 5249 pesos, 19 percent off”

Update June 23, 2023:

Problem with verbalization of numbers and how we solve it

We encountered a problem in the MoneyAmount component, when used with an amount greater than 5 digits (eg 100,000), the VoiceOver announced each digit separately and not as a complete number, but when used with 5 digits or less it announced them correctly (eg 20,000, 5,000, 30).

Solution:

The component was receiving the numbers as a text (String), therefore the VoiceOver was reading the digits separately and not as a whole. Taking this into account, the corresponding modifications were made so that the number it receives is in the DecimalStyle format and that Voiceover reads it correctly.

The fix was to add two new properties (numberValue and numberFormatted), which modify the value received in MoneyAmount as an NSNumber and change its format to be decimal style. numberFormatted is used in the accessibilityLabel so that voiceOver reads it with the set format. Below is the code before and after the fix.

Before:

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

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

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()) "

Conclusion

At Mercado Libre, we value every person’s experience when using the products of the Meli ecosystem. UIKit offers us a wide range of tools to improve the accessibility of our components as well as user experience. As you have seen, accessibility is a very important factor in our apps since we want all users to have a pleasant experience, regardless of their environment.

--

--