Addaw, anar a la pàgina d'inici Donar

Lliçó 5 de 10 · 14 min de lectura · Revisada el 26/10/2026

Navegació, focus i estructura de pantalles: títols, ordre, diàlegs i teclat extern

Estàs llegint una lliçó oberta. Matricula't per fer els tests i els exercicis, desar el teu progrés i aconseguir el certificat.

Matricula-m'hi · nivell 6 (119 €)

A les lliçons d'iOS i Android vas veure com descriure cada control: nom, rol, estat i accions. Però una aplicació no és un conjunt de controls solts, sinó un recorregut entre pantalles, fulls, diàlegs i menús. Per a qui fa servir VoiceOver, TalkBack, Switch Control o un teclat extern, la pregunta constant és «on soc i on és el focus?». Aquesta lliçó tracta d'aquesta capa: títols de pantalla, ordre de recorregut, encapçalaments, gestió del focus en navegar i en obrir o tancar finestres modals, i funcionament amb teclat. És, a la pràctica, la font de molts dels errors més greus d'una auditoria mòbil, perquè bloquegen tasques completes.

Dos focus diferents: el d'accessibilitat i el d'entrada

Al web només hi ha un focus. Al mòbil en conviuen dos, i confondre'ls és l'origen de molts errors:

  • Focus d'accessibilitat: l'element que el lector de pantalla està llegint, marcat amb un requadre (el «cursor de VoiceOver» o el rectangle verd de TalkBack). Es mou amb els gestos de lliscar i es pot situar sobre qualsevol element accessible, inclosos els textos no interactius.
  • Focus d'entrada: l'element que rep les pulsacions d'un teclat físic o de Full Keyboard Access, i el camp on escriu el teclat en pantalla. Només el reben els elements enfocables (controls i camps).

A Android la diferència és explícita: requestFocus() mou el focus d'entrada, mentre que el focus d'accessibilitat es mou amb performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null). A Compose, un FocusRequester mou el focus d'entrada; si el que necessites és que TalkBack llegeixi un element concret, comprova-ho sempre amb TalkBack actiu, perquè no n'hi ha prou que el camp tingui el cursor. A iOS, el focus de VoiceOver es dirigeix amb les notificacions .screenChanged i .layoutChanged (UIKit) o amb @AccessibilityFocusState (SwiftUI), i el de teclat amb el sistema de focus d'UIKit (UIFocusSystem) o @FocusState a SwiftUI.

Punt clau

En dissenyar un canvi de pantalla o l'aparició d'un diàleg, decideix explícitament on ha de quedar cada focus. Si no ho decideixes tu, ho decideix el sistema, i de vegades el deixa en un element que ja no existeix o darrere de la finestra que acabes d'obrir.

Títols de pantalla i encapçalaments

El criteri 2.4.2 Pàgina titulada no es trasllada de manera literal: WCAG2ICT l'aplica al programari en conjunt i l'EN 301 549 v3.2.1 (PDF, s'obre en una finestra nova) el deixa buit al capítol 11. Tot i així, que cada pantalla tingui un títol que la identifiqui és la pràctica esperada i la manera de saber on s'és. No n'hi ha prou que es vegi: la tecnologia de suport l'ha d'anunciar en arribar-hi.

  • iOS: UINavigationController i NavigationStack anuncien el canvi i VoiceOver situa el focus al principi de la nova pantalla. Fes servir navigationItem.title o .navigationTitle("Les meves cites") sempre, encara que la barra s'amagui per disseny. En les transicions personalitzades, publica UIAccessibility.post(notification: .screenChanged, argument: tituloLabel).
  • Android (Views): TalkBack anuncia el títol de l'activitat en canviar de finestra. Amb una sola activitat i diversos fragments, actualitza el títol o fes servir ViewCompat.setAccessibilityPaneTitle(vista, "Les meves cites") al contenidor del fragment perquè el canvi s'anunciï.
  • Compose: marca el contenidor de cada destinació amb Modifier.semantics { paneTitle = "Les meves cites" } i fes servir un TopAppBar amb títol visible.

Dins de la pantalla, els encapçalaments (.header a iOS, heading() a Compose, accessibilityHeading a Views) permeten saltar per seccions amb el rotor de VoiceOver o els controls de lectura de TalkBack. Són l'equivalent funcional dels skip links (enllaços per saltar blocs) del web: en una pantalla de fitxa amb deu blocs, marcar cada títol de secció com a encapçalament estalvia desenes de gestos.

Ordre de recorregut

Els lectors recorren els elements en un ordre calculat a partir de la jerarquia i la posició a la pantalla: de dalt a baix i, a cada fila, en el sentit de lectura. Aquest ordre falla amb disposicions en columnes, targetes amb elements superposats o botons flotants. Els criteris afectats són l'1.3.2 Seqüència significativa i el 2.4.3 Ordre del focus.

NecessitatiOSAndroid
Definir l'ordre dels fillsaccessibilityElements (UIKit), .accessibilitySortPriority (SwiftUI)accessibilityTraversalBefore/After (Views), traversalIndex (Compose)
Recórrer un bloc sencer abans de sortir-neshouldGroupAccessibilityChildren, .accessibilityElement(children: .contain)isTraversalGroup = true (Compose); a Views, un ViewGroup contenidor comú, perquè el recorregut segueix la jerarquia
Moure el focus després d'un canvi.layoutChanged amb argument, @AccessibilityFocusStateACTION_ACCESSIBILITY_FOCUS, live region o paneTitle
Amagar el que hi ha al darrereaccessibilityViewIsModal, .isModalimportantForAccessibility="noHideDescendants", Dialog

Corregeix l'ordre amb aquestes API només quan la disposició no es pugui reorganitzar: la primera opció és que l'ordre del codi coincideixi amb l'ordre visual lògic. Un cas típic és el botó flotant d'acció («Nova incidència»): visualment és a baix a la dreta, però conceptualment és una acció principal; col·loca'l al recorregut just després del títol o del contingut que el motiva, no al final d'una llista de cent elements.

Infografia · Procés

Com gestionar un canvi de pantalla o un diàleg

Cinc passos perquè qui fa servir VoiceOver, TalkBack o el teclat sàpiga sempre on és

  1. Pas 1: Anunciar el títolLa pantalla o el diàleg té un títol que el lector anuncia en arribar-hi
  2. Pas 2: Col·locar el focusAl títol o al primer element lògic, mai al final ni al darrere
  3. Pas 3: Amagar el fonsAmb el diàleg obert, el que queda al darrere no es pot recórrer
  4. Pas 4: Permetre tancarBotó visible, gest d'escapament de VoiceOver i botó Enrere d'Android
  5. Pas 5: Retornar el focusEn tancar, el focus torna al control que va obrir el diàleg
ADDAW · Cursos d'accessibilitat
Llegir la infografia com a text

Procés de cinc passos en seqüència per gestionar un canvi de pantalla o l'obertura d'un diàleg en una aplicació. Pas 1, anunciar el títol: la nova pantalla o el diàleg s'identifica amb un títol que el lector de pantalla anuncia en arribar-hi. Pas 2, col·locar el focus: el focus va al títol o al primer element lògic, mai al final ni al darrere. Pas 3, amagar el fons: mentre el diàleg és obert, el que queda al darrere no es pot recórrer. Pas 4, permetre tancar: amb un botó visible, amb el gest d'escapament de VoiceOver i amb el botó Enrere d'Android. Pas 5, retornar el focus: en tancar, el focus torna al control que va obrir el diàleg o a un altre lloc lògic.

Diàlegs, fulls inferiors i menús

Una finestra modal accessible compleix quatre condicions: el focus hi entra en obrir-se, el contingut del darrere queda inaccessible mentre és oberta, es pot tancar sense gestos especials i, en tancar-se, el focus torna a un lloc lògic (normalment el control que la va obrir). Els components del sistema (UIAlertController, .sheet, .confirmationDialog, AlertDialog, ModalBottomSheet, DropdownMenu) ho compleixen gairebé tot de sèrie. Els problemes arriben amb les capes personalitzades: una vista superposada dins de la mateixa pantalla que, per al lector, no és modal.

// UIKit: capa personalitzada que es comporta com a modal
final class FiltroOverlay: UIView {
    override func didMoveToSuperview() {
        super.didMoveToSuperview()
        accessibilityViewIsModal = true          // ignora el que hi ha al darrere
        UIAccessibility.post(notification: .screenChanged, argument: tituloLabel)
    }
    // Gest d'escapament de VoiceOver (dos dits en «Z»)
    override func accessibilityPerformEscape() -> Bool {
        cerrar(); return true
    }
}

// SwiftUI: capa pròpia
PanelFiltros()
    .accessibilityAddTraits(.isModal)
    .accessibilityAction(.escape) { mostrarFiltros = false }

A Android, si la capa és una vista dins de la mateixa finestra, marca el contingut del fons amb importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS mentre sigui oberta i restaura'l en tancar; gestiona el botó Enrere del sistema (OnBackPressedCallback o BackHandler a Compose) per tancar-la. A Compose, la via segura és fer servir Dialog o ModalBottomSheet, que creen la seva pròpia finestra; si construeixes una capa a mà, aplica Modifier.clearAndSetSemantics { } al contingut del fons mentre sigui oberta.

Error freqüent

Un full inferior personalitzat amb els filtres de cerca s'obre sobre la llista, però TalkBack continua recorrent la llista que queda al darrere: l'usuari sent resultats en lloc de filtres i no sap que hi ha un full obert. En tancar-lo amb «Aplica», el focus es perd i torna al principi de la pantalla. Incompleix el 2.4.3 Ordre del focus i, segons el cas, l'1.3.2 i el 4.1.2. La correcció: un component modal del sistema o una capa amb el fons amagat, un títol anunciat en obrir i el focus retornat al botó «Filtres» en tancar.

Teclat extern i focus visible

Moltes persones amb discapacitat motriu fan servir el mòbil o la tauleta amb un teclat Bluetooth o amb un commutador que l'emula. A iOS, Full Keyboard Access (Configuració > Accessibilitat > Teclats) permet moure's amb Tab i les fletxes i activar amb la barra espaiadora; a Android, Tab i les fletxes mouen el focus d'entrada i Retorn activa. S'apliquen els mateixos criteris que al web:

  • 2.1.1 Teclat i 2.1.2 Sense paranys per al focus del teclat: tot control és abastable i es pot sortir de qualsevol component (un mapa, un reproductor, un camp de text multilínia).
  • 2.4.7 Focus visible: els components estàndard dibuixen l'indicador; als controls propis cal pintar-lo (estat focused a Views, onFocusChanged o Modifier.focusable amb indicació a Compose, isFocused a UIFocusEnvironment).
  • 2.4.11 Focus no ocult (mínim): l'element amb el focus no queda tapat completament per barres fixes, bàners o el mateix teclat en pantalla. Desplaça el contingut perquè el camp actiu quedi a la vista.

A Compose, Modifier.focusProperties { next = siguiente } i els grups de focus permeten ajustar el recorregut de teclat quan l'ordre per defecte no serveix; a Views, nextFocusDown, nextFocusForward i similars.

Prova-ho tu

Tria una aplicació que facis servir cada dia (el teu banc, el transport de la teva ciutat). Activa VoiceOver o TalkBack i obre un menú, un filtre o un diàleg de confirmació. Comprova tres coses: s'anuncia el títol en obrir-se?, pots arribar amb gestos de lliscar al que hi ha al darrere?, on queda el focus en tancar-lo? Anota'n el resultat; el faràs servir al cas pràctic.

Resum

  • Al mòbil hi ha dos focus, el d'accessibilitat i el d'entrada; decideix on queda cadascun a cada canvi de pantalla i comprova el resultat amb el lector real.
  • Cada pantalla té un títol que s'anuncia en arribar-hi (pràctica lligada al 2.4.2): navigationTitle, títol d'activitat, paneTitle; els encapçalaments permeten saltar per seccions.
  • L'ordre de recorregut segueix la lògica de la tasca (1.3.2, 2.4.3); corregeix-lo amb les API d'ordre només si no pots reorganitzar la disposició.
  • Una capa modal rep el focus, amaga el fons, es tanca amb el gest d'escapament o el botó Enrere i retorna el focus al control que la va obrir.
  • Amb teclat extern s'apliquen el 2.1.1, el 2.1.2, el 2.4.7 i el 2.4.11: tot abastable, sense paranys i amb un focus visible que res no tapa.

Portes 8 lliçons d'aquest curs: que no es perdin

Crea el teu compte i desa el que ja has llegit i segueix el curs amb tot:

  • El test d'aquesta lliçó (3 preguntes) per comprovar el que has après
  • Exercicis pràctics amb pàgines reals
  • El teu progrés desat: continues on ho vas deixar, des de qualsevol dispositiu
  • Constància del curs amb les hores i certificat del nivell 6 quan aprovis l'examen

Entres amb el teu compte de Google o només amb el teu correu electrònic. No cal contrasenya.

Matricula-m'hi · nivell 6 (119 €)