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

Lliçó 3 de 10 · 11 min de lectura · Revisada el 26/10/2026

iOS: VoiceOver, Dynamic Type, API d'accessibilitat d'UIKit i SwiftUI, Voice Control

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 €)

iOS té una de les piles d'accessibilitat més madures del mercat, i els components estàndard d'UIKit i SwiftUI són accessibles per defecte. Els problemes apareixen amb vistes personalitzades, imatges com a botons, agrupacions incorrectes i mides fixes. Aquesta lliçó recorre les eines de l'usuari (VoiceOver, Dynamic Type, Voice Control) i les API amb què el desenvolupador descriu la interfície.

VoiceOver en 3 minuts per a desenvolupadors

Activa VoiceOver amb una triple pulsació del botó lateral (configura-ho a Configuració > Accessibilitat > Drecera d'accessibilitat) o demanant-ho a Siri. Gestos essencials: un toc per seleccionar i sentir, lliscar a la dreta/esquerra per anar a l'element següent/anterior, doble toc per activar, tres dits per desplaçar, dos dits en «Z» per tornar enrere, rotor (girar dos dits) per canviar entre encapçalaments, enllaços, controls de formulari, etc. Doble toc i mantenir permet arrossegar. El que VoiceOver llegeix de cada element, en ordre: etiqueta (label), valor, trets (traits: botó, encapçalament, seleccionat, ajustable…) i pista (hint). Si l'element té accions personalitzades, s'anuncia «accions disponibles» i es recorren lliscant amunt/avall.

L'API d'UIAccessibility a UIKit

Cada UIView exposa propietats que alimenten l'arbre d'accessibilitat:

  • isAccessibilityElement: si la vista és un element (true per defecte als controls i a les etiquetes; false a les vistes contenidores).
  • accessibilityLabel: el nom. Breu, sense incloure el tipus («Envia», no «Botó envia»).
  • accessibilityValue: el valor actual (el percentatge d'un control lliscant, el text d'un camp, «3 de 5»).
  • accessibilityTraits: .button, .link, .header, .image, .selected, .notEnabled, .adjustable, .updatesFrequently…
  • accessibilityHint: què passarà («Obre el detall del moviment»). Opcional, i l'usuari pot desactivar les pistes.
  • accessibilityElements en un contenidor per definir l'ordre dels fills; shouldGroupAccessibilityChildren perquè VoiceOver els llegeixi junts.
  • accessibilityCustomActions: accions alternatives als gestos (eliminar, arxivar, moure).
  • accessibilityUserInputLabels: noms alternatius per a Voice Control.

Exemple: una cel·la de moviment bancari composta per tres etiquetes i una icona. Sense intervenció, VoiceOver llegeix quatre elements separats («Supermercat», «−42,30», «15 de març», «imatge»). El correcte és agrupar-los en un sol element amb una etiqueta composta i accions:

final class MovimientoCell: UITableViewCell {
    func configurar(con m: Movimiento) {
        conceptoLabel.text = m.concepto
        importeLabel.text = m.importeFormateado
        fechaLabel.text = m.fechaFormateada
        iconoView.image = m.icono

        // Un sol element accessible per cel·la
        isAccessibilityElement = true
        accessibilityLabel = "\(m.concepto), \(m.importeAccesible), \(m.fechaLarga)"
        accessibilityTraits = .button
        accessibilityHint = "Obre el detall del moviment"
        accessibilityCustomActions = [
            UIAccessibilityCustomAction(name: "Categoritza") { _ in
                self.delegate?.categorizar(m); return true
            },
            UIAccessibilityCustomAction(name: "Comparteix") { _ in
                self.delegate?.compartir(m); return true
            }
        ]
        // La icona és decorativa
        iconoView.isAccessibilityElement = false
    }
}

Fixa't en importeAccesible: «−42,30 €» es llegeix millor com «menys quaranta-dos euros amb trenta cèntims» o almenys «−42,30 euros»; el símbol es pot llegir malament segons la veu. Per als botons d'icona, assigna sempre accessibilityLabel («Cerca», «Menú») o VoiceOver llegirà el nom del fitxer («ic_search_24»).

Anuncis i canvis de pantalla

Per notificar VoiceOver fes servir UIAccessibility.post(notification:argument:): .announcement amb un text per als missatges d'estat (4.1.3), .layoutChanged amb la vista que ha de rebre el focus quan canvia una part de la pantalla, i .screenChanged quan apareix una pantalla o un modal nou. A SwiftUI, AccessibilityNotification.Announcement("Desat").post() des d'iOS 17.

SwiftUI

SwiftUI deriva molta accessibilitat de la declaració (un Button amb text ja té nom i rol). Modificadors clau:

struct FilaMovimiento: View {
    let m: Movimiento
    var body: some View {
        HStack {
            Image(systemName: m.simbolo)
                .accessibilityHidden(true)          // decoratiu
            VStack(alignment: .leading) {
                Text(m.concepto).font(.body)
                Text(m.fechaFormateada).font(.caption)
            }
            Spacer()
            Text(m.importeFormateado)
                .font(.body.monospacedDigit())
        }
        .accessibilityElement(children: .combine)   // un sol element
        .accessibilityLabel("\(m.concepto), \(m.importeAccesible), \(m.fechaLarga)")
        .accessibilityAddTraits(.isButton)
        .accessibilityAction(named: "Categoritza") { categorizar(m) }
        .accessibilityAction(named: "Comparteix") { compartir(m) }
    }
}

// Encapçalament de secció
Text("Moviments de març")
    .font(.title2)
    .accessibilityAddTraits(.isHeader)

// Imatge informativa
Image("grafico-gastos")
    .accessibilityLabel("Despeses per categoria: alimentació 40 %, transport 25 %, oci 20 %, altres 15 %")

Altres modificadors útils: .accessibilityValue per als estats («Activat»), .accessibilityAdjustableAction per als controls de tipus lliscant personalitzats, .accessibilitySortPriority per a l'ordre dins d'un grup, @AccessibilityFocusState per moure el focus de VoiceOver (per exemple, al primer error d'un formulari), .accessibilityInputLabels per a Voice Control, i .accessibilityRepresentation perquè una vista personalitzada es comporti com un control estàndard.

Error freqüent

Botons fets amb Image + .onTapGesture a SwiftUI o amb UIImageView + UITapGestureRecognizer a UIKit. VoiceOver els anuncia com a «imatge», sense el tret de botó, i de vegades ni tan sols són enfocables. Fes servir Button o UIButton: hereten el rol, el focus, l'activació amb doble toc i Voice Control sense esforç.

Dynamic Type

Dynamic Type és el sistema d'escalat del text d'iOS: l'usuari tria entre 12 mides (7 d'estàndard i 5 d'accessibilitat). L'aplicació el respecta si:

  • A UIKit fa servir UIFont.preferredFont(forTextStyle: .body) (o UIFontMetrics per a fonts personalitzades) i activa adjustsFontForContentSizeCategory = true a les etiquetes, els botons i els camps.
  • A SwiftUI fa servir els estils (.font(.body), .headline…); s'escalen automàticament. Per a les mesures associades al text (icones, espaiats) fes servir @ScaledMetric.
  • Les etiquetes tenen numberOfLines = 0 i les restriccions permeten créixer; a SwiftUI evita .lineLimit(1) i les alçades fixes.
  • En mides d'accessibilitat grans, canvia la disposició: @Environment(\.dynamicTypeSize) permet passar d'HStack a VStack quan dynamicTypeSize.isAccessibilitySize. A UIKit, consulta traitCollection.preferredContentSizeCategory.isAccessibilityCategory.
  • Les barres de pestanyes i de navegació mostren versions ampliades en prémer llargament si es fan servir els components estàndard.

Prova amb la mida «AX5» i amb «Text en negreta» activat: és la prova de foc de l'1.4.4.

Voice Control i Full Keyboard Access

Voice Control (Configuració > Accessibilitat) permet controlar iOS amb la veu: «Toca Envia», «Mostra els números», «Mostra els noms». Funciona amb l'accessibilityLabel: si el botó d'icona no té etiqueta, l'usuari no el pot anomenar. Si l'etiqueta visible és llarga, afegeix accessibilityUserInputLabels = ["Envia", "Tramet"] per oferir alternatives curtes. Full Keyboard Access permet fer servir un teclat Bluetooth amb Tab; els controls estàndard ho admeten, les vistes personalitzades necessiten canBecomeFocused i la gestió d'UIFocusEnvironment. Switch Control recorre els elements accessibles en ordre, per la qual cosa un ordre i una agrupació ben fets també el beneficien.

Altres ajustos del sistema que l'aplicació ha de respectar

  • UIAccessibility.isReduceMotionEnabled / @Environment(\.accessibilityReduceMotion): desactiva les animacions no essencials.
  • isReduceTransparencyEnabled, isDarkerSystemColorsEnabled (Augmentar el contrast), isBoldTextEnabled: fes servir els colors del sistema o defineix variants d'alt contrast al catàleg de colors.
  • buttonShapesEnabled: mostra un subratllat o una vora als botons de només text si està activat.
  • isVoiceOverRunning: útil per ajustar comportaments (per exemple, no fer avançar automàticament un carrusel), mai per oferir una «versió» diferent de l'aplicació.

Exemple

Formulari d'alta: en prémer «Continua» amb un camp buit, l'aplicació marca el camp en vermell. Per a VoiceOver s'hi afegeix: el missatge d'error com a text visible associat al camp (l'accessibilityLabel del camp inclou «Error: el NIF és obligatori» o s'enllaça amb accessibilityValue), un anunci «Hi ha 1 error al formulari» amb .announcement, i es mou el focus al primer camp amb error mitjançant @AccessibilityFocusState. Així es compleixen el 3.3.1, el 4.1.3 i el 2.4.3.

Resum

  • VoiceOver llegeix l'etiqueta, el valor, els trets i la pista; el desenvolupador els defineix amb accessibilityLabel, accessibilityValue, accessibilityTraits i accessibilityHint (o els seus modificadors de SwiftUI).
  • Agrupa les cel·les compostes en un element (isAccessibilityElement / .accessibilityElement(children: .combine)) i exposa les accions amb custom actions.
  • Fes servir Button/UIButton per als botons, marca els encapçalaments amb .header, amaga el que és decoratiu i anuncia els canvis amb notificacions d'accessibilitat.
  • Dynamic Type: fonts preferides, adjustsFontForContentSizeCategory, sense línies ni alçades fixes, disposició alternativa en les mides d'accessibilitat.
  • Voice Control depèn de les etiquetes; respecta Reduir el moviment, Augmentar el contrast i Text en negreta.

Portes 3 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 €)