Accesibilidad en iOS 27: apps de lectura y controles personalizados
Leer contenido extenso es un problema distinto al de navegar por una interfaz: el objetivo es moverse con fluidez a través del texto, no saltar entre controles, y las dos sesiones de accesibilidad de la WWDC26 se reparten exactamente a lo largo de esa costura: una para la superficie de lectura, otra para los controles que la rodean.
La distinción importa porque las correcciones difieren en su naturaleza. Las fallas de una app de lectura tienen que ver con la continuidad: texto que no se conecta entre párrafos, una lectura completa que se detiene al final de una página. Las de un control personalizado tienen que ver con la traducción: un gesto que lo transmite todo visualmente y nada a VoiceOver. iOS 27 incorpora API dirigidas a ambos frentes, y una de ellas, accessibilityLinkedGroup, es nueva este año.
TL;DR
- Las apps de lectura deberían recurrir primero a las vistas de texto del sistema.
UITextView,TextEditoryTextcon la selección activada adoptan el protocoloUITextInputy obtienen gratis la navegación por línea, palabra y carácter, además de la selección1. - Cuando el diseño obliga a separar elementos de texto, enlázalos para que VoiceOver pueda cruzar la costura. iOS 18 introdujo
accessibilityNextTextNavigationElement/accessibilityPreviousTextNavigationElement; iOS 27 añade el modificador de SwiftUIaccessibilityLinkedGrouppara lograr el mismo efecto1. - Para el contenido paginado, el rasgo
causesPageTurnjunto conaccessibilityScrollhace que Speak Screen y VoiceOver avancen las páginas automáticamente durante una lectura completa1. - El texto dibujado a medida (páginas escaneadas, tipografía avanzada) pierde todo eso. Adoptar
UITextInputpor completo lo restaura: la geometría medianteselectionRects, las subcadenas mediantetextInRangey un tokenizador para la navegación por línea, palabra y carácter1. - Los controles personalizados siguen cuatro principios rectores: propósito, valor, acciones y respuesta. Las herramientas son
accessibilityLabel/accessibilityValue, el rasgo.adjustableconaccessibilityAdjustableAction, acciones personalizadas para controles de varios ejes y el toque directo (allowsDirectInteraction) para superficies cargadas de gestos2.
Apps de lectura: conectar el texto que el diseño separó
La sesión sobre apps de lectura se construye en torno a una restricción engañosamente simple. La app de guía de viajes del presentador usa una UITextView distinta para cada párrafo porque el diseño lo exigía, en lugar de una sola vista que contenga toda la página1. Cada vista de texto es accesible por sí sola. El problema aparece en la frontera entre ellas.
El presentador fija tres metas para la app: navegación granular por el texto para que VoiceOver y Speak Screen se muevan con fluidez, una experiencia de lectura continua sin interrupciones y la selección completa del texto1. El resto de la sesión es un recorrido por la API que satisface cada una.
Para la navegación entre vistas separadas, la respuesta está en las API de elementos de navegación por texto introducidas en iOS 18. Para cada elemento de texto, devuelves el elemento de texto accesible siguiente y anterior al que VoiceOver debe moverse. En el ejemplo de la sesión, el párrafo 1 devuelve el párrafo 2 desde su accessibilityNextTextNavigationElement, y el párrafo 2 devuelve el párrafo 1 desde su accessibilityPreviousTextNavigationElement1. Una vez conectado, VoiceOver pasa del final de un párrafo a la primera línea del siguiente en lugar de reproducir el sonido de callejón sin salida.
La novedad de iOS 27 vive en SwiftUI. Como lo plantea el presentador, a partir de iOS 27, enlazar varios elementos de texto con el modificador accessibilityLinkedGroup logra el mismo efecto1. Les das a los elementos enlazados el mismo id y namespace, y heredan el comportamiento de navegación por texto entre elementos sin la gestión manual de siguiente y anterior. AppKit recibe el equivalente accessibilitySharedTextUIElements para el mismo resultado en Mac1. El artículo De qué está hecho SwiftUI del clúster explica cómo modificadores de SwiftUI como este se resuelven hasta el árbol de accesibilidad subyacente.
La continuidad es la segunda meta. El contenido paginado requiere deslizar, y una lectura completa debería ignorar los límites de página como lo hace un audiolibro. En la sesión, Speak Screen se detiene en seco al final de la primera página hasta que el presentador aplica el rasgo causesPageTurn al último párrafo de cada página. Junto con accessibilityScroll, Speak Screen y VoiceOver pasan entonces automáticamente a la página siguiente al llegar al final, y el rasgo está disponible tanto en UIKit como en SwiftUI1.
La tercera meta, la selección, en su mayor parte viene gratis con las vistas de texto del sistema, pero la sesión añade un detalle bien pensado: una acción «Guardar recomendación» expuesta a través del rotor de edición de VoiceOver. El presentador sobrescribe accessibilityCustomActions en la vista de texto del párrafo y construye la acción personalizada con la categoría de edición, precisamente para que aparezca en el rotor de edición junto a las operaciones de selección de texto y no como una acción genérica1. La indicación es explícita: usa la categoría de edición cuando una acción personalizada esté asociada a la selección de texto.
Cuando dibujas tu propio texto: UITextInput por completo
La segunda mitad de la sesión de lectura aborda el caso en que las vistas del sistema no son una opción. El texto personalizado aparece en apps de lectura dedicadas a la tipografía avanzada, en código compartido entre apps o en páginas escaneadas, y el ejemplo del presentador es el más agudo: reemplazar las vistas de texto de la guía de viajes por páginas escaneadas de un cuaderno escrito a mano. El costo es total. Cambiar a imágenes pierde el comportamiento de accesibilidad que UITextView ofrecía gratis, hasta lo más básico: leer el texto en voz alta. VoiceOver simplemente dice «Imagen»1.
La solución es adoptar el protocolo UITextInput, que puede asentarse en cualquier elemento de accesibilidad y hacer que el texto dibujado o el texto dentro de las imágenes sea tan accesible como una vista de texto estándar1. El detalle, dicho con franqueza en la sesión, es que hay que implementarlo en su totalidad para obtener todo el beneficio. El presentador repasa las piezas que sostienen la estructura:
- Geometría.
selectionRectscalcula los rectángulos de resaltado para un rango dado. A partir de una imagen manuscrita, el presentador usa la altura y el ancho conocidos de cada línea para aproximar los rectángulos mediante una funciónselectionRectFromImagepersonalizada, y luego devuelve el arreglo ensamblado1. - Subcadenas.
textInRangedevuelve solo la porción de texto que consulta una tecnología de asistencia1. - Un tokenizador. La navegación por línea, oración, palabra o carácter pasa por un tokenizador. La sesión crea una subclase del
UITextInputStringTokenizerde UIKit para ajustarse al diseño personalizado1.
Un refinamiento es explícitamente opcional. Para que la selección se sienta completa, con tiradores y resaltados, el presentador añade una UITextInteraction a la vista de la página y llama al delegado de entrada cuando cambia la selección para que el sistema actualice lo visual. La sesión señala que este paso no lo exige UITextInput en sí; redondea la experiencia para igualar una vista de texto estándar1. Y UITextInput se compone con las API anteriores, de modo que causesPageTurn y los elementos de navegación también funcionan con texto personalizado.
Hay una recompensa que la sesión destaca y que es fácil de subestimar: este trabajo no solo sirve a VoiceOver y Speak Screen. Desde iOS 26, Accessibility Reader puede abrir el contenido de una app en una pantalla ajustada para una lectura más fácil, y las mismas prácticas de texto accesible también mejoran esa experiencia1.
Controles personalizados: propósito, valor, acciones, respuesta
La sesión sobre controles personalizados abre con un control deslizante estándar de SwiftUI y un argumento sobre por qué funciona. Lees una pista, un tirador colocado a la mitad, una pista de que se puede arrastrar y una respuesta inmediata, todo de un vistazo. Nadie explicó nada de eso. Entonces la sesión hace la pregunta obvia: ¿y si alguien no puede ver la pantalla? VoiceOver responde leyendo «Brillo, 50 %, ajustable», más una pista para deslizar hacia arriba o hacia abajo, lo que transmite las mismas cuatro cosas que el visual: el propósito, el valor, la acción disponible y la respuesta a medida que cambia el valor2.
.adjustable con una acción ajustable.
Esas cuatro palabras (propósito, valor, acciones, respuesta) son los principios rectores de la sesión, y cada ejemplo remite a ellas2. El primero es un control de dispensador de café: arrastra hacia arriba para más café, hacia abajo para menos, y el nivel de llenado representa las onzas. Antes de cualquier trabajo, VoiceOver lo lee como un genérico «Botón, 6 onzas», sin idea de cómo cambiar el valor2. Las correcciones son incrementales:
- Propósito y valor.
accessibilityLabello nombra «Dispensador de café»;accessibilityValueanuncia el llenado actual2. - Acción. El rasgo
.adjustablele dice a VoiceOver que el control responde a deslizamientos hacia arriba y hacia abajo, yaccessibilityAdjustableActionproporciona una clausura con un parámetro de dirección.incremento.decrementpara manejar cada caso2.
Eso logra el ajuste de una onza a la vez. Para un control más fino, la sesión recurre al gesto de paso integrado en VoiceOver: un doble toque sostenido que comienza en el accessibilityActivationPoint del control y envía los eventos táctiles directamente al control a medida que el dedo se mueve. El presentador ajusta el punto de activación para que coincida con el nivel de llenado actual2. La respuesta durante el paso es una pequeña lección de contención: la sesión publica un anuncio solo cuando el valor cambió de verdad y han transcurrido al menos 0,3 segundos, porque anunciar cada cambio sería ruidoso2.
El panel ecualizador sube el listón. Es un control bidimensional, y la sesión es franca al señalar que .adjustable es la herramienta equivocada, porque sus acciones de incremento y decremento cubren un solo eje. La respuesta son las acciones personalizadas: el modificador accessibilityAction aplicado cuatro veces para «mover arriba», «mover a la derecha», «mover abajo» y «mover a la izquierda», cada una desplazando un eje un paso fijo acotado al rango2. A diferencia de la acción ajustable, las acciones personalizadas admiten cualquier operación que definas, y también llegan a las personas que usan Switch Control y Voice Control2.
Toque directo: cuando los gestos son lo esencial
El último ejemplo de la sesión es un control de gato virtual donde acaricias, tocas y pellizcas para obtener distintas reacciones. El paso encaja mal aquí, señala el presentador, porque la gente quizá quiera repetir una acción una y otra vez o usar varios gestos2. Así que el control recurre al toque directo.
.accessibilityDirectTouch con .requiresActivation a un control gobernado por gestos, de modo que los toques pasan directos al gato en lugar de ser interceptados por VoiceOver.
El rasgo allowsDirectInteraction marca una región como zona de toque directo: los eventos táctiles pasan directos al control en lugar de ser procesados por VoiceOver, de modo que funciona cada gesto que el control admite2. Dos opciones moldean el comportamiento. .requiresActivation mantiene el control inerte hasta un doble toque, lo que permite arrastrar por la pantalla sin activarlo por accidente, y el toque directo permanece activo hasta que el foco abandona el elemento. .silentOnTouch mantiene a VoiceOver en silencio sobre la región, pensado para controles que producen su propio audio que el habla de VoiceOver, de lo contrario, taparía2. El gato virtual usa .accessibilityDirectTouch con .requiresActivation2.
La sesión cierra con una advertencia que enlaza con el argumento de la accesibilidad como plataforma desarrollado en La accesibilidad como plataforma: no todo el mundo puede ejecutar gestos de toque directo, así que, siempre que sea posible, expón otra vía, como las acciones personalizadas, para que las personas que usan Switch Control y Voice Control alcancen las mismas interacciones2.
Guía de adopción
Ambas sesiones terminan con la misma instrucción: activa VoiceOver y audita tu propia app. En concreto:
- Para una superficie de lectura sobre vistas de texto del sistema, prueba el gesto de lectura completa, navega con el rotor de líneas y selecciona texto. Si una lectura completa se detiene en un límite de página, adopta
causesPageTurnconaccessibilityScroll. Si la navegación por línea llega a un callejón sin salida entre elementos de texto separados, enlázalos con las API de elementos de navegación (UIKit) oaccessibilityLinkedGroup(SwiftUI, iOS 27)1. - Si dibujas tu propio texto, planifica una adopción completa de
UITextInput, no una parcial; el protocolo es todo o nada, y el paso opcionalUITextInteractiones lo que hace que la selección se sienta nativa1. - Para cualquier control personalizado, recorre los cuatro principios en orden. ¿Puede una persona que usa VoiceOver saber qué es (etiqueta), en qué estado está (valor), qué puede hacer (rasgo ajustable o acciones personalizadas) y qué ocurrió (anuncios)? Reserva el toque directo para los controles cuyo valor es el gesto en sí, y combínalo con una alternativa no gestual2.
El tema recurrente: recurre primero a los componentes del sistema, y trata la vía personalizada como la excepción que exige un trabajo real y completo. El artículo Las tres superficies de una app de iOS plantea la accesibilidad como una superficie de primer orden, junto a la interfaz visible y App Intents; estas sesiones muestran cómo se ve acertar con esa superficie en el caso del texto y los controles.
Preguntas frecuentes
¿Cuál es la nueva API de accesibilidad de iOS 27 para apps de lectura?
El modificador de SwiftUI accessibilityLinkedGroup. A partir de iOS 27, enlazar varios elementos de texto con el mismo id y namespace les da navegación por texto entre elementos, de modo que VoiceOver pasa de la última línea de un elemento a la primera línea del siguiente. Es el equivalente en SwiftUI de las API de iOS 18 accessibilityNextTextNavigationElement/accessibilityPreviousTextNavigationElement y de accessibilitySharedTextUIElements de AppKit1.
¿Necesito implementar UITextInput si uso una vista de texto estándar?
No. UITextView (UIKit), TextEditor y Text con la selección activada (SwiftUI), y NSTextView (AppKit) ya adoptan UITextInput y ofrecen de fábrica la navegación por línea, palabra y carácter, además de la selección. Solo adoptas UITextInput por tu cuenta cuando dibujas texto personalizado, como páginas escaneadas o tipografía avanzada, donde esos comportamientos del sistema se pierden1.
¿Cuándo debe un control personalizado usar el rasgo ajustable frente a acciones personalizadas?
Usa el rasgo .adjustable con accessibilityAdjustableAction para valores de un solo eje donde el incremento y el decremento tienen sentido, como un control deslizante. Usa acciones personalizadas (el modificador accessibilityAction) cuando un solo eje no basta, como un panel bidimensional, o cuando quieres exponer operaciones discretas que VoiceOver lee por su nombre. El panel ecualizador de la sesión usa cuatro acciones personalizadas (mover arriba/derecha/abajo/izquierda) precisamente porque el rasgo ajustable cubre una sola dirección2.
¿Qué es el toque directo y cuándo debo usarlo?
El toque directo (el rasgo allowsDirectInteraction, aplicado mediante .accessibilityDirectTouch en SwiftUI) marca una región para que los toques pasen directos a tu control en lugar de ser procesados por VoiceOver, lo que permite usar cada gesto que el control admite. Úsalo para controles cargados de gestos donde el gesto de paso encaja mal, y combínalo con .requiresActivation para evitar activaciones accidentales. Ofrece siempre una alternativa no gestual, como las acciones personalizadas, para quienes no pueden ejecutar gestos de toque directo2.
¿Cómo ayuda el texto accesible a funciones más allá de VoiceOver?
El mismo trabajo rinde frutos en Speak Screen y, desde iOS 26, en Accessibility Reader, que abre el contenido de una app en una pantalla ajustada para una lectura más fácil. Implementar las prácticas de navegación por texto, paso de página y UITextInput que cubre la sesión de lectura mejora las tres experiencias a partir de un solo conjunto de cambios1.
Lecturas relacionadas
- La accesibilidad como plataforma: Personal Voice, Live Speech, seguimiento ocular, Music Haptics
- De qué está hecho SwiftUI
- Las tres superficies de una app de iOS
- La superficie de widgets y controles de iOS 26
- Hub: Serie Apple Ecosystem
- Guía: Desarrollo de agentes de iOS
Referencias
-
Apple, sesión 219 de la WWDC26, «Enhance the accessibility of your reading app». developer.apple.com/videos/play/wwdc2026/219. Fuente sobre las vistas de texto del sistema (
UITextView,TextEditor,Textcon selección,NSTextView) que adoptanUITextInput; las API de iOS 18accessibilityNextTextNavigationElement/accessibilityPreviousTextNavigationElementy el modificador de SwiftUI de iOS 27accessibilityLinkedGroup(AppKit:accessibilitySharedTextUIElements);causesPageTurnconaccessibilityScroll; la acción de selección de texto medianteaccessibilityCustomActionscon la categoría de edición; la adopción completa deUITextInputpara texto personalizado (selectionRects,textInRange,UITextInputStringTokenizer) más laUITextInteractionopcional; y Accessibility Reader de iOS 26. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple, sesión 220 de la WWDC26, «Refine accessibility for custom controls». developer.apple.com/videos/play/wwdc2026/220. Fuente sobre los principios de propósito/valor/acciones/respuesta; el control de dispensador de café que usa
accessibilityLabel,accessibilityValue, el rasgo.adjustableyaccessibilityAdjustableAction; el gesto de paso en elaccessibilityActivationPointcon anuncios limitados (valor cambiado más 0,3 segundos transcurridos); el modificadoraccessibilityActionpara el panel ecualizador; y el toque directo medianteallowsDirectInteraction(.accessibilityDirectTouch) con.requiresActivationy.silentOnTouchpara el control de gato virtual, además del recordatorio de la alternativa no gestual. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩