← Todos os Posts

Acessibilidade no iOS 27: apps de leitura e controles personalizados

Ler conteúdo longo é um problema diferente de navegar por uma interface: o objetivo é percorrer o texto de forma fluida, e não saltar entre controles. As duas sessões de acessibilidade da WWDC26 se dividem exatamente ao longo dessa fronteira: uma para a superfície de leitura, outra para os controles que a cercam.

Essa divisão importa porque as soluções diferem em natureza. As falhas de um app de leitura têm a ver com continuidade: texto que não se conecta entre parágrafos, uma leitura completa que para no fim de uma página. As falhas de um controle personalizado têm a ver com tradução: um gesto que comunica tudo visualmente e nada ao VoiceOver. O iOS 27 traz APIs voltadas para ambos os casos, e uma delas, accessibilityLinkedGroup, é novidade neste ano.

TL;DR

  • Apps de leitura devem recorrer primeiro às visualizações de texto do sistema. UITextView, TextEditor e Text com seleção habilitada adotam o protocolo UITextInput e ganham de graça a navegação por linha/palavra/caractere e a seleção1.
  • Quando o layout obriga a separar elementos de texto, vincule-os para que o VoiceOver consiga atravessar a fronteira. O iOS 18 introduziu accessibilityNextTextNavigationElement/accessibilityPreviousTextNavigationElement; o iOS 27 acrescenta o modificador SwiftUI accessibilityLinkedGroup para o mesmo efeito1.
  • Para conteúdo paginado, o trait causesPageTurn combinado com accessibilityScroll faz com que o Falar Tela e o VoiceOver avancem de página automaticamente durante uma leitura completa1.
  • Texto renderizado de forma própria (páginas digitalizadas, tipografia avançada) perde tudo isso. Adotar UITextInput por completo o restaura: a geometria via selectionRects, as substrings via textInRange e um tokenizador para a navegação por linha/palavra/caractere1.
  • Controles personalizados seguem quatro princípios norteadores: propósito, valor, ações e retorno. As ferramentas são accessibilityLabel/accessibilityValue, o trait .adjustable com accessibilityAdjustableAction, ações personalizadas para controles de múltiplos eixos e o toque direto (allowsDirectInteraction) para superfícies dominadas por gestos2.

Apps de leitura: reconectando o texto que o layout separou

A sessão sobre apps de leitura gira em torno de uma restrição enganosamente simples. O app de guia de viagens do apresentador usa um UITextView separado para cada parágrafo porque o layout assim exigia, em vez de uma única visualização contendo a página inteira1. Cada visualização de texto é acessível por si só. O problema aparece na fronteira entre elas.

Watch on Apple Developer ↗
A Apple mostra o VoiceOver preso navegando linha a linha dentro de um parágrafo, incapaz de avançar para o próximo porque cada parágrafo é uma visualização separada, e em seguida os conecta com as APIs de elementos de navegação por texto.

O apresentador define três metas para o app: navegação granular por texto, para que o VoiceOver e o Falar Tela percorram o texto de forma fluida; uma experiência de leitura contínua, sem interrupções; e seleção completa de texto1. O restante da sessão é um passeio por qual API atende cada uma delas.

Para a navegação entre visualizações separadas, a resposta são as APIs de elementos de navegação por texto introduzidas no iOS 18. Para cada elemento de texto, você retorna o próximo e o anterior elemento de texto acessível para o qual o VoiceOver deve se mover. No exemplo da sessão, o parágrafo 1 retorna o parágrafo 2 a partir de seu accessibilityNextTextNavigationElement, e o parágrafo 2 retorna o parágrafo 1 a partir de seu accessibilityPreviousTextNavigationElement1. Uma vez configurado, o VoiceOver avança além do fim de um parágrafo e chega à primeira linha do seguinte, em vez de tocar o som de beco sem saída.

A novidade do iOS 27 está no SwiftUI. Como o apresentador coloca, a partir do iOS 27, vincular vários elementos de texto usando o modificador accessibilityLinkedGroup produz o mesmo efeito1. Você atribui aos elementos vinculados o mesmo id e o mesmo namespace, e eles herdam o comportamento de navegação por texto entre elementos sem a contabilidade manual de próximo/anterior. O AppKit ganha o equivalente accessibilitySharedTextUIElements para o mesmo resultado no Mac1. O post Do que o SwiftUI é feito, do mesmo cluster, mostra como modificadores SwiftUI como este se resolvem até a árvore de acessibilidade subjacente.

A continuidade é a segunda meta. Conteúdo paginado exige deslizar, e uma leitura completa deveria ignorar as fronteiras de página do jeito que um audiolivro faz. Na sessão, o Falar Tela para de vez no fim da primeira página até o apresentador aplicar o trait causesPageTurn ao último parágrafo de cada página. Combinados com accessibilityScroll, o Falar Tela e o VoiceOver então rolam para a página seguinte automaticamente ao chegar ao fim, e o trait está disponível tanto no UIKit quanto no SwiftUI1.

A terceira meta, a seleção, vem em grande parte de graça com as visualizações de texto do sistema, mas a sessão acrescenta um toque cuidadoso: uma ação “Salvar Recomendação” exposta pelo rotor de edição do VoiceOver. O apresentador sobrescreve accessibilityCustomActions na visualização de texto do parágrafo e cria a ação personalizada com a categoria de edição, justamente para que ela apareça no rotor de edição ao lado das operações de seleção de texto, e não como uma ação genérica1. A orientação é explícita: use a categoria de edição quando uma ação personalizada estiver associada à seleção de texto.

Quando você renderiza seu próprio texto: UITextInput por inteiro

A segunda metade da sessão de leitura trata do caso em que as visualizações do sistema não são uma opção. Texto personalizado aparece em apps de leitura dedicados a tipografia avançada, em código compartilhado entre apps ou em páginas digitalizadas, e o exemplo do apresentador é o mais contundente: substituir as visualizações de texto do guia de viagens por páginas digitalizadas de um caderno manuscrito. O custo é total. Trocar por imagens perde o comportamento de acessibilidade que o UITextView oferecia de graça, até a coisa mais básica: ler o texto em voz alta. O VoiceOver apenas diz “Imagem”1.

A solução é adotar o protocolo UITextInput, que pode ficar sobre qualquer elemento de acessibilidade e tornar o texto renderizado ou o texto em imagens tão acessível quanto uma visualização de texto padrão1. O detalhe, dito sem rodeios na sessão, é que você precisa implementá-lo na íntegra para colher o benefício completo. O apresentador percorre as peças que sustentam tudo:

  • Geometria. selectionRects calcula os retângulos de destaque para um intervalo dado. Partindo de uma imagem de escrita à mão, o apresentador usa a altura e a largura conhecidas de cada linha para aproximar os retângulos por meio de uma função personalizada selectionRectFromImage e, em seguida, retorna o array montado1.
  • Substrings. textInRange retorna apenas a parte do texto que uma tecnologia assistiva consulta1.
  • Um tokenizador. A navegação por linha, frase, palavra ou caractere passa por um tokenizador. A sessão cria uma subclasse do UITextInputStringTokenizer do UIKit para se adequar ao layout personalizado1.

Um refinamento é explicitamente opcional. Para que a seleção pareça completa, com alças e destaques, o apresentador adiciona um UITextInteraction à visualização da página e chama o delegate de entrada quando a seleção muda, de modo que o sistema atualize os elementos visuais. A sessão observa que esse passo não é exigido pelo próprio UITextInput; ele apenas arredonda a experiência para igualá-la à de uma visualização de texto padrão1. E o UITextInput se combina com as APIs anteriores, de forma que causesPageTurn e os elementos de navegação também funcionam em texto personalizado.

Há um retorno que a sessão destaca e que é fácil de subestimar: fazer esse trabalho não serve apenas ao VoiceOver e ao Falar Tela. Desde o iOS 26, o Leitor de Acessibilidade pode abrir o conteúdo de um app em uma exibição ajustada para uma leitura mais fácil, e as mesmas práticas de texto acessível também melhoram essa experiência1.

Controles personalizados: propósito, valor, ações, retorno

A sessão de controles personalizados abre com um slider padrão do SwiftUI e um argumento sobre por que ele funciona. Você lê uma trilha, uma alça posicionada no meio, um indício de que é arrastável e um retorno imediato, tudo num relance. Ninguém explicou nada disso. Então a sessão faz a pergunta óbvia: e se alguém não consegue enxergar a tela? O VoiceOver responde lendo “Brilho, 50%, ajustável” mais uma dica para deslizar para cima ou para baixo, o que comunica as mesmas quatro coisas que aparecem visualmente: o propósito, o valor, a ação disponível e o retorno conforme o valor muda2.

Watch on Apple Developer ↗
A Apple transforma um controle personalizado de dispensador de café de um simples “botão, 6 onças” em um slider ajustável que o VoiceOver consegue operar, adicionando rótulo, valor e o trait .adjustable com uma ação ajustável.

Essas quatro palavras (propósito, valor, ações, retorno) são os princípios norteadores da sessão, e todo exemplo se remete a elas2. O primeiro é um controle de dispensador de café: arraste para cima para mais café, para baixo para menos, com o nível de preenchimento representando as onças. Antes de qualquer trabalho, o VoiceOver o lê como um genérico “Botão, 6 onças”, sem nenhuma noção de como mudar o valor2. As correções são incrementais:

  1. Propósito e valor. accessibilityLabel o nomeia como “Dispensador de Café”; accessibilityValue anuncia o preenchimento atual2.
  2. Ação. O trait .adjustable diz ao VoiceOver que o controle responde a deslizes para cima/baixo, e accessibilityAdjustableAction fornece um closure com um parâmetro de direção, .increment ou .decrement, para tratar cada caso2.

Isso permite o ajuste de uma onça por vez. Para um controle mais fino, a sessão recorre ao gesto de passagem embutido no VoiceOver: um toque duplo e segurar que começa no accessibilityActivationPoint do controle e envia os eventos de toque diretamente para o controle conforme o dedo se move. O apresentador define o ponto de ativação para corresponder ao nível de preenchimento atual2. O retorno durante a passagem é uma pequena lição de contenção: a sessão só posta um anúncio quando o valor de fato mudou e ao menos 0,3 segundo se passou, porque anunciar cada mudança seria ruidoso2.

O painel de equalizador eleva o nível. É um controle bidimensional, e a sessão é franca ao dizer que .adjustable é a ferramenta errada, porque suas ações de incremento/decremento cobrem um único eixo. A resposta são as ações personalizadas: o modificador accessibilityAction aplicado quatro vezes para “mover para cima”, “mover para a direita”, “mover para baixo” e “mover para a esquerda”, cada uma deslocando um eixo por um passo fixo limitado ao intervalo2. Ao contrário da ação ajustável, as ações personalizadas suportam qualquer operação que você defina, e também alcançam quem usa o Controle por Botão e o Controle por Voz2.

Toque direto: quando os gestos são todo o ponto

O exemplo final da sessão é um controle de gato virtual em que você faz carinho, toca e pinça para reações diferentes. A passagem é uma má escolha aqui, observa o apresentador, porque as pessoas podem querer repetir uma ação várias vezes ou usar vários gestos2. Por isso o controle recorre ao toque direto.

Watch on Apple Developer ↗
A Apple adiciona o modificador .accessibilityDirectTouch com .requiresActivation a um controle guiado por gestos para que os toques passem direto para o gato em vez de serem interceptados pelo VoiceOver.

O trait allowsDirectInteraction marca uma região como área de toque direto: os eventos de toque passam direto para o controle em vez de serem processados pelo VoiceOver, de modo que todo gesto que o controle suporta funcione2. Duas opções moldam o comportamento. .requiresActivation mantém o controle inerte até um toque duplo, o que permite à pessoa arrastar pela tela sem acioná-lo por acidente, e o toque direto então permanece ativo até o foco deixar o elemento. .silentOnTouch mantém o VoiceOver em silêncio sobre a região, pensado para controles que produzem seu próprio áudio, que a fala do VoiceOver de outra forma sobreporia2. O gato virtual usa .accessibilityDirectTouch com .requiresActivation2.

A sessão se encerra com uma ressalva que retoma o argumento da acessibilidade como plataforma em Acessibilidade como plataforma: nem todos conseguem realizar gestos de toque direto, então sempre que possível ofereça outro caminho, como ações personalizadas, para que quem usa o Controle por Botão e o Controle por Voz alcance as mesmas interações2.

Orientações de adoção

Ambas as sessões terminam com a mesma instrução: ligue o VoiceOver e audite o seu próprio app. Em termos concretos:

  • Para uma superfície de leitura sobre visualizações de texto do sistema, experimente o gesto de leitura completa, navegue com o rotor de linhas e selecione texto. Se uma leitura completa parar numa fronteira de página, adote causesPageTurn com accessibilityScroll. Se a navegação por linha chegar a um beco sem saída entre elementos de texto separados, vincule-os com as APIs de elementos de navegação (UIKit) ou com accessibilityLinkedGroup (SwiftUI, iOS 27)1.
  • Se você renderiza seu próprio texto, planeje uma adoção completa do UITextInput, não parcial; o protocolo é tudo ou nada, e o passo opcional do UITextInteraction é o que faz a seleção parecer nativa1.
  • Para qualquer controle personalizado, percorra os quatro princípios em ordem. Quem usa o VoiceOver consegue dizer o que ele é (rótulo), em que estado está (valor), o que pode fazer (trait ajustável ou ações personalizadas) e o que aconteceu (anúncios)? Reserve o toque direto para controles cujo valor é o próprio gesto, e combine-o com uma alternativa não gestual2.

O tema recorrente: recorra primeiro aos componentes do sistema e trate o caminho personalizado como a exceção que exige um trabalho real e completo. O post As três superfícies de um app iOS enquadra a acessibilidade como uma superfície de primeira classe, ao lado da interface visível e dos App Intents; essas sessões mostram o que é acertar essa superfície no caso do texto e dos controles.

Perguntas frequentes

Qual é a nova API de acessibilidade do iOS 27 para apps de leitura?

O modificador SwiftUI accessibilityLinkedGroup. A partir do iOS 27, vincular vários elementos de texto com o mesmo id e namespace lhes dá navegação por texto entre elementos, de forma que o VoiceOver vai da última linha de um elemento para a primeira linha do seguinte. É o equivalente em SwiftUI das APIs accessibilityNextTextNavigationElement/accessibilityPreviousTextNavigationElement do iOS 18 e do accessibilitySharedTextUIElements do AppKit1.

Preciso implementar UITextInput se eu usar uma visualização de texto padrão?

Não. UITextView (UIKit), TextEditor e Text com seleção habilitada (SwiftUI), e NSTextView (AppKit) já adotam UITextInput e fornecem a navegação por linha/palavra/caractere mais a seleção de imediato. Você só adota UITextInput por conta própria quando renderiza texto personalizado, como páginas digitalizadas ou tipografia avançada, onde esses comportamentos do sistema se perdem1.

Quando um controle personalizado deve usar o trait ajustável em vez de ações personalizadas?

Use o trait .adjustable com accessibilityAdjustableAction para valores de eixo único em que incrementar e decrementar fazem sentido, como um slider. Use ações personalizadas (o modificador accessibilityAction) quando um eixo não basta, como num painel bidimensional, ou quando você quer expor operações discretas que o VoiceOver lê pelo nome. O painel de equalizador da sessão usa quatro ações personalizadas (mover para cima/direita/baixo/esquerda) justamente porque o trait ajustável cobre apenas uma direção2.

O que é o toque direto e quando devo usá-lo?

O toque direto (o trait allowsDirectInteraction, aplicado via .accessibilityDirectTouch no SwiftUI) marca uma região para que os toques passem direto ao seu controle em vez de serem processados pelo VoiceOver, deixando as pessoas usarem todos os gestos que o controle suporta. Use-o em controles dominados por gestos onde o gesto de passagem se encaixa mal, e combine-o com .requiresActivation para evitar acionamentos acidentais. Ofereça sempre uma alternativa não gestual, como ações personalizadas, para quem não consegue realizar gestos de toque direto2.

Como o texto acessível ajuda recursos além do VoiceOver?

O mesmo trabalho compensa no Falar Tela e, desde o iOS 26, no Leitor de Acessibilidade, que abre o conteúdo de um app em uma exibição ajustada para uma leitura mais fácil. Implementar as práticas de navegação por texto, virada de página e UITextInput que a sessão de leitura aborda melhora as três experiências a partir de um único conjunto de mudanças1.

Leitura relacionada

Referências


  1. Apple, sessão 219 da WWDC26, “Enhance the accessibility of your reading app.” developer.apple.com/videos/play/wwdc2026/219. Fonte para a adoção de UITextInput pelas visualizações de texto do sistema (UITextView, TextEditor, Text com seleção, NSTextView); as APIs accessibilityNextTextNavigationElement/accessibilityPreviousTextNavigationElement do iOS 18 e o modificador SwiftUI accessibilityLinkedGroup do iOS 27 (AppKit: accessibilitySharedTextUIElements); causesPageTurn com accessibilityScroll; a ação de seleção de texto via accessibilityCustomActions com a categoria de edição; a adoção completa de UITextInput para texto personalizado (selectionRects, textInRange, UITextInputStringTokenizer) mais o UITextInteraction opcional; e o Leitor de Acessibilidade do iOS 26. 

  2. Apple, sessão 220 da WWDC26, “Refine accessibility for custom controls.” developer.apple.com/videos/play/wwdc2026/220. Fonte para os princípios de propósito/valor/ações/retorno; o controle de dispensador de café usando accessibilityLabel, accessibilityValue, o trait .adjustable e accessibilityAdjustableAction; o gesto de passagem no accessibilityActivationPoint com anúncios limitados (valor alterado mais 0,3 segundo decorrido); o modificador accessibilityAction para o painel de equalizador; e o toque direto via allowsDirectInteraction (.accessibilityDirectTouch) com .requiresActivation e .silentOnTouch para o controle de gato virtual, mais o lembrete da alternativa não gestual. 

Artigos relacionados

Performance e interoperabilidade do SwiftUI no iOS 27

Como o SwiftUI do iOS 27 lida com a rolagem de lazy stacks, efeitos de shader na GPU e a interoperabilidade com AppKit/U…

23 min de leitura

O que há de novo no SwiftUI para o iOS 27

O iOS 27 reformula listas, documentos, toolbars e erros do SwiftUI: arrastar-para-reordenar, um modelo de documento legí…

24 min de leitura

O stack de agentes do design engineer

Design engineers precisam de infraestrutura de agentes que garanta consistência visual, disciplina tipográfica, conformi…

13 min de leitura