Ekran smartfona z nakładką drzewa dostępności — węzły i etykiety widoczne obok kodu aplikacji mobilnej w środowisku programistycznym
Image description: Ekran smartfona z nakładką drzewa dostępności — węzły i etykiety widoczne obok kodu aplikacji mobilnej w środowisku programistycznym

Przewodnik inżynierski · Mobilne API dostępności

Natywne API dostępności na urządzeniach mobilnych w 2026: UIAccessibility, AccessibilityNode i sieć

Porównanie iOS UIAccessibility, Android AccessibilityNodeInfo oraz wieloplatformowych pomostów, które próbują je pogodzić — co odwzorowuje się czysto, co nie i gdzie mobilna sieć zawodzi.

Natywne API dostępności na urządzeniach mobilnych w 2026:
UIAccessibility, AccessibilityNode i sieć

iOS i Android udostępniają czytnikowi ekranu platformy w pełni rozbudowane drzewo dostępności — jednak te dwa drzewa nie zgadzają się ze sobą w niczym poza podstawami: etykietą i rolą. Zmapowaliśmy każdy prymityw faktycznie obsługiwany przez VoiceOver i TalkBack w 2026 roku, sposób, w jaki React Native, Flutter i Kotlin Multiplatform próbują je łączyć, oraz miejsce, gdzie dostępność mobilnych widoków WebView cicho się załamuje.

2
porównywane natywne API
3
wieloplatformowe pomosty
28
zmapowanych prymitywów
13 min czytania
Aktualizacja: maj 2026

1. iOS UIAccessibility — etykiety, cechy, wskazówki, wartości

Każdy widoczny element na ekranie iOS ma lub może mieć reprezentację w drzewie dostępności. Apple udostępnia tę reprezentację przez nieformalny protokół UIAccessibility, implementowany przez UIView i każdą kontrolkę systemową, oraz przez klasę UIAccessibilityElement — lekką klasę alokowaną dla tych fragmentów interfejsu, które są rysowane, lecz same nie są widokami: znaki na niestandardowym wykresie, glify wewnątrz sceny Core Graphics, regiony wewnątrz CALayer. VoiceOver, Switch Control, Full Keyboard Access i Voice Control — wszystkie korzystają z tego samego protokołu; nauka raz daje dostęp do czterech technologii wspomagających.

Protokół udostępnia cztery prymitywy istotne dla niemal każdego ekranu. Accessibility label to krótka, czytelna dla człowieka nazwa elementu — „Wyślij“, „Zdjęcie profilowe Ashy“, „Wstecz“. Accessibility traits to maska bitowa flag przypominających role — .button, .header, .image, .selected, .adjustable, .staticText, .updatesFrequently — informujące VoiceOver, jak zachować się wobec elementu i które gesty aktywować. Accessibility value to tekstowa reprezentacja bieżącego stanu („Włączone“, „75%“, „Czwartek, 22 maja“). Accessibility hint to dłuższe, opcjonalne wyjaśnienie („Stuknij dwukrotnie, by otworzyć przeglądarkę zdjęć“), które VoiceOver wypowiada po chwili zwłoki, gdy użytkownik nie zareaguje na samą etykietę.

Cztery prymitywy łączą się ze sobą. Przełącznik odczytywany jest jako etykieta + cecha + wartość: „Wi-Fi, przełącznik, Włączone“. Suwak odczytywany jest jako etykieta + cecha + wartość + wskazówka: „Głośność, regulowany, 60 procent, przesuń w górę lub w dół, aby dostosować“. Niestandardowy słupek wykresu to łańcuch obiektów UIAccessibilityElement, z których każdy ma etykietę, wartość i ramkę wewnątrz kontenera. Ten łańcuch jest powierzchnią API — VoiceOver przemierza go liniowo podczas gestu przesuwania w prawo i respektuje kolejność, w jakiej elementy są publikowane przez tablicę accessibilityElements kontenera.

SwiftUI to ten sam protokół, z bardziej przyjazną fasadą

Modyfikatory widoku .accessibilityLabel(), .accessibilityValue(), .accessibilityHint() i .accessibilityAddTraits() w SwiftUI kompilują się do tych samych właściwości UIAccessibility na bazowym UIView. SwiftUI dodaje również .accessibilityElement(children:), które rozwiązuje problem „znaków na wykresie“ w sposób bardziej deklaratywny niż podejście UIKit — jednak kontrakt runtime widziany przez VoiceOver jest identyczny. Warto poznać nazewnictwo UIKit, bo każdy przykład Apple, każda odpowiedź na Stack Overflow i każdy audyt dostępności posługują się właśnie nimi.


2. Android AccessibilityNodeInfo — role, akcje, importantForAccessibility

Android przyjmuje inne podejście. Tam gdzie iOS przyczepia dostępność do płaskiego protokołu na każdym widoku, Android serializuje całe drzewo dostępności jako graf obiektów AccessibilityNodeInfo, z których każdy jest migawką widoku w chwili nadejścia zapytania TalkBack. Framework tworzy migawki leniwie; View publikuje swój węzeł przez nadpisanie metody onInitializeAccessibilityNodeInfo() (lub w Compose — przez ustawienie modyfikatorów semantycznych), a platforma splata relacje rodzic-dziecko w drzewo odzwierciedlające hierarchię widoków.

Prymitywy różnią się od iOS-owych w trzech istotnych aspektach. Po pierwsze, Android udostępnia rolę przez pole className typowane jako ciąg znaków — android.widget.Button, android.widget.CheckBox, android.widget.EditText. TalkBack odczytuje nazwę klasy i decyduje, jak ogłosić element („przycisk“, „pole wyboru“, „pole edycji“). Compose tłumaczy swoje semantyki Role.Button, Role.Checkbox, Role.RadioButton na to samo pole. Rola jest bardziej szczegółowa niż maska bitowa cech iOS, ale też bardziej sztywna — nie ma roli „całkowicie niestandardowej“, chyba że akceptuje się komunikat „widok“.

Po drugie, Android reprezentuje interaktywność jako zbiór akcji przypisanych do węzła: ACTION_CLICK, ACTION_LONG_CLICK, ACTION_SCROLL_FORWARD, ACTION_SET_TEXT, ACTION_FOCUS oraz długa lista niestandardowych akcji, które można zarejestrować za pomocą AccessibilityNodeInfo.AccessibilityAction. TalkBack udostępnia niestandardowe akcje przez „rotator akcji“ — użytkownik przesuwa jednym palcem w górę i słyszy kolejne nazwy akcji. iOS ma analogiczną koncepcję (UIAccessibilityCustomAction), lecz na Androidzie lista akcji jest powierzchnią; na iOS powierzchnią jest słownik gestów.

Po trzecie, Android posiada importantForAccessibility — wyliczenie per-widok (auto, yes, no, noHideDescendants) kontrolujące, czy węzeł w ogóle pojawia się w drzewie. noHideDescendants to najpotężniejsze narzędzie dostępności Androida i jednocześnie najczęściej pomijane — usuwa cały poddrzew z przeglądania przez TalkBack, odpowiednik aria-hidden=“true” w sieci. iOS nie ma dokładnego odpowiednika; najbliższe jest ustawienie accessibilityElements na pustą tablicę na kontenerze, co usuwa jedynie bezpośrednie dzieci kontenera, nie całe poddrzewo.

Niezgodność „live region“

Android udostępnia ViewCompat.setAccessibilityLiveRegion() z trzema wartościami: none, polite, assertive. Słownictwo odzwierciedla ARIA — prawie. TalkBack honoruje poziomy grzeczności niezawodnie. iOS nie ma nic porównywalnego na poziomie protokołu: aktualizacje ogłasza się przez wywołanie UIAccessibility.post(notification: .announcement, argument: “Zapisano”), imperatywne jednorazowe wywołanie, które nie jest powiązane z żadnym widokiem. Wieloplatformowe pomosty muszą emulować jedno na bazie drugiego, a niedopasowanie impedancji widać w każdym frameworku omówionym w sekcji 3.


3. Wieloplatformowe pomosty — React Native, Flutter, Kotlin Multiplatform

Każdy wieloplatformowy framework mobilny musi wziąć dwa opisane powyżej API i zaprezentować jedną, zunifikowaną po stronie frameworka powierzchnię. Żadnemu nie udaje się to w pełni. Trzy podejścia dominują na rynku w 2026 roku — React Native, Flutter oraz Kotlin Multiplatform z Compose Multiplatform — każde z nich to nieco inny kompromis między wyciekiem szczegółów a poziomem abstrakcji.

React Native 0.76
Pomost JS do natywnego UIKit i Android View
Najwyraźniejsze odwzorowanie — i najbardziej nieszczelne
Pomost iOSWłaściwości accessibilityLabel, accessibilityHint, accessibilityRole, accessibilityState na Pressable i View mapują się prawie 1:1 na UIAccessibility — jednak nazwy ról to słownictwo React Native, nie iOS.
Pomost AndroidTe same właściwości JS mapują się na AccessibilityNodeInfo przez adapter po stronie Yoga; accessibilityRole=“button” ustawia className na android.widget.Button.
PułapkaWłaściwość accessibilityLiveRegion działa tylko na Androidzie — na iOS cicho nic nie robi i trzeba ręcznie wywołać AccessibilityInfo.announceForAccessibility().
Flutter 3.27
Własny rendering · syntetyczne drzewo dostępności
Najbardziej jednolite — i najbardziej nieprzezroczyste
PodejścieFlutter renderuje wszystko na kanwie Skia, więc buduje własne drzewo SemanticsNode i serializuje je do platformy na żądanie.
Ścieżka iOSWęzły SemanticsNode są tłumaczone na instancje UIAccessibilityElement we widoku Flutter, z cechami mapowanymi z zestawów SemanticsAction i SemanticsFlag.
Ścieżka AndroidTo samo drzewo SemanticsNode jest serializowane do węzłów AccessibilityNodeInfo przez widok Fluttera na Androida; akcje stają się AccessibilityActions; live region staje się SemanticsFlag.isLiveRegion.
Kotlin Multiplatform · Compose Multiplatform
Współdzielony runtime Compose · dostępność per-target
Najnowsze, z najbardziej widocznymi szwami platformowymi
PodejścieModyfikator Compose Modifier.semantics { } definiuje role i akcje raz; każdy target tłumaczy ten sam blok semantyczny na własne natywne API dostępności.
Target iOSRuntime Compose-for-iOS przemierza drzewo semantyczne i tworzy UIAccessibilityElements — jednak implementacja iOS jest młodsza niż Androidowa i nadal brakuje jej kilku rodzajów semantycznych.
Target AndroidDojrzała ścieżka: semantyki stają się AccessibilityNodeInfo przez tę samą warstwę compose-ui-semantics, z której korzysta natywny Compose na Androida.

Wzorzec jest we wszystkich trzech przypadkach taki sam: syntetyczne, uformowane przez framework drzewo semantyczne po jednej stronie, dwa uformowane przez platformę drzewa dostępności po drugiej, i translator pośrodku, który dobrze radzi sobie z prostymi przypadkami, a z złożonymi — ze znaczącą utratą wierności. Proste przypadki — przycisk z etykietą, obraz z tekstem alternatywnym, nagłówek — przechodzą przez pomost bez strat. Złożone — niestandardowy gest dwoma palcami, wykres, którego elementy powinny być fokusowaną grupą, live region, który na iOS musi zadziałać bez powiązania z widokiem — ujawniają słownictwo bazowej platformy w wieloplatformowym kodzie lub po prostu nie dają się przetłumaczyć.

„Pierwsze 80 procent dostępności mobilnej jest identyczne we wszystkich frameworkach. Ostatnie 20 procent to miejsce, w którym każdy framework zdradza, w którym natywnym API naprawdę myśli.“

— Dział inżynierski Disability World, maj 2026

4. Luka WebView — kiedy dostępność mobilnej sieci cicho zawodzi

Zarówno iOS, jak i Android renderują treści webowe przez systemowy WebView — WKWebView na iOS, android.webkit.WebView (lub Chrome Custom Tabs) na Androidzie. W obu przypadkach WebView jest czarną skrzynką z perspektywy aplikacji hosta: aplikacja widzi jeden widok, lecz czytnik ekranu widzi całe drzewo dostępności DOM wewnątrz. Pomost między tymi dwoma drzewami to miejsce, w którym zadziwiająco duża część dostępności mobilnej po cichu zawodzi.

Mechanizm jest pozornie prosty. Gdy fokus czytnika ekranu wchodzi do WebView, platforma odczytuje drzewo dostępności dokumentu bezpośrednio z silnika przeglądarki — WebKit na iOS, Blink na Androida — i przemierza je jako poddrzewo drzewa aplikacji hosta. Role, etykiety i atrybuty ARIA w sieci są tłumaczone na słownictwo platformy w czasie rzeczywistym. Element button bez jawnej roli wewnątrz WebView odczytywany jest jako przycisk na obu platformach; region aria-live=“polite” ogłaszany jest poprawnie na obu; aria-label na łączu pojawia się jako etykieta dostępności łącza. Przez pierwsze kilka lat mobilnej sieci po prostu działało.

Przepaść pojawia się w trzech miejscach. Po pierwsze, niestandardowe gesty zdefiniowane w aplikacji hosta — gest dwoma palcami do zamknięcia, magic-tap do odtwarzania i pauzy — są niewidoczne dla treści WebView; uruchamiają się na złym celu lub nie uruchamiają się wcale, gdy fokus jest wewnątrz dokumentu. Po drugie, obiekty UIAccessibilityElement hosta rysowane nad WebView (pływający przycisk akcji, niestandardowy pasek narzędzi) konkurują z drzewem WebView o kolejność przeglądania, a wynikowy porządek czytania jest niedeterministyczny w różnych wersjach iOS. Po trzecie — i to jest największy pojedynczy błąd w dostępności mobilnej sieci — WebView na iOS nie honoruje poziomów grzeczności aria-live tak jak Safari w karcie: pomost ogłoszeń WKWebView pomija rozróżnienie między polite a assertive, więc każda aktualizacja na żywo traktowana jest jako polite bez względu na znaczniki.

Dwa spojrzenia na ten sam DOM
W karcie Mobile Safari
<div role="alert" aria-live="assertive">
  Utracono połączenie — ponawiam próbę.
</div>

VoiceOver w normalnej karcie Safari przerywa bieżącą wypowiedź i natychmiast odczytuje komunikat. Poziom grzeczności assertive jest honorowany w całym potoku WebKit.

Ten sam DOM wewnątrz WKWebView
<div role="alert" aria-live="assertive">
  Utracono połączenie — ponawiam próbę.
</div>

Ten sam znacznik, ten sam silnik przeglądarki — ale pomost dostępności WKWebView do UIKit obniża rangę ogłoszenia do odroczonego komunikatu polite. Użytkownik słyszy je po chwili zwłoki, czasem już po tym, gdy wpisał dane do uszkodzonego formularza.

Wieloplatformowa naprawa, która naprawdę działa

W przypadku ogłoszeń wewnątrz WebView jedynym niezawodnym wzorcem wieloplatformowym w 2026 roku jest udostępnienie mostu JavaScript do aplikacji hosta — małego handlera postMessage — i kierowanie asertywnych ogłoszeń poza DOM, do hosta, a następnie z powrotem przez UIAccessibility.post(notification: .announcement, …) na iOS lub announceForAccessibility() na Androida. Web aria-live sprawdza się tylko przy naprawdę grzecznych komunikatach, gdzie akceptowalne jest kilkusekundowe opóźnienie.


5. Tabela odwzorowań — co odpowiada czemu

Zmapowaliśmy 28 prymitywów faktycznie obsługiwanych przez VoiceOver i TalkBack w praktyce — sumę powierzchni protokołu iOS UIAccessibility, powierzchni Android AccessibilityNodeInfo oraz najczęściej używanych właściwości wieloplatformowych React Native i Flutter. Poniższa tabela zawiera wyłącznie sporne wiersze: prymitywy, gdzie odwzorowanie jest niekompletne, asymetryczne lub zaskakujące. Wiersze z czystym odwzorowaniem (etykieta, rola przycisku, rola obrazu, nagłówek) zostały pominięte ze względu na długość.

MożliwośćiOS UIAccessibilityAndroid AccessibilityNodeInfoReact Native 0.76Flutter 3.27
Tekst wskazówki (dłuższe wyjaśnienie)accessibilityHinttooltipText (API 28+)accessibilityHint (tylko iOS)SemanticsProperties.hint
Poziom grzeczności live regionN/D — wyłącznie imperatywny postsetAccessibilityLiveRegion()accessibilityLiveRegion (tylko Android)SemanticsFlag.isLiveRegion
Ukrycie poddrzewa przed dostępnościąaccessibilityElementsHidden (tylko dzieci)importantForAccessibility=“noHideDescendants”accessibilityElementsHidden / importantForAccessibilityWidget ExcludeSemantics
Niestandardowa akcja (rotor / menu)UIAccessibilityCustomActionAccessibilityNodeInfo.AccessibilityActionaccessibilityActions + onAccessibilityActionSemanticsAction z niestandardową etykietą
Semantyka regulowanego / suwakaUIAccessibilityTraitAdjustable + accessibilityIncrementRangeInfo + ACTION_SCROLL_FORWARDaccessibilityRole=“adjustable” + handlerySlider udostępnia SemanticsAction.increase
Poziom nagłówkaUIAccessibilityTraitHeader (bez poziomu)setHeading(true) (bez poziomu)accessibilityRole=“header” (bez poziomu)SemanticsProperties.headingLevel (1–6)
Stan wybrany / przełączonyUIAccessibilityTraitSelectedsetSelected(true) + setCheckable()accessibilityState={selected, checked}SemanticsFlag.isSelected
Semantyka grupy / kontenerashouldGroupAccessibilityChildrensetScreenReaderFocusable(true)accessible={true} na rodzicuWidget MergeSemantics
Jednorazowe ogłoszenieUIAccessibility.post(.announcement, …)view.announceForAccessibility()AccessibilityInfo.announceForAccessibility()SemanticsService.announce()

Z tabeli wynikają trzy wzorce. Po pierwsze, asymetria wokół live regionów to największe źródło rozbieżności wieloplatformowych — Android posiada ustawienie grzeczności per-widok, iOS ma jedynie globalny imperatywny post, a każdy framework powyżej jest zmuszony ukrywać tę różnicę. Po drugie, poziomy nagłówków to jedyne miejsce, gdzie Flutter realnie przewyższa obie natywne platformy; prymitywy iOS i Androida wiedzą tylko, że „to jest nagłówek“, nie że „to jest H3 pod H2“. Po trzecie, prymityw „ukryj przed dostępnością“ jest bardziej elastyczny na Androidzie — noHideDescendants ukrywa całe poddrzewo jednym ruchem, podczas gdy iOS wymaga ukrywania dzieci każdego kontenera osobno.


6. Poradnik praktyczny dla mobilnych deweloperów

1

Poznaj natywne słownictwo przed słownictwem frameworka

Każdy wieloplatformowy pomost — React Native, Flutter, Compose Multiplatform — ma własne nazewnictwo właściwości dostępności, a każda z tych nazw to drobne kłamstwo o tym, co naprawdę robi bazowa platforma. Gdy czytnik ekranu nie ogłasza poprawnie, błąd prawie zawsze tkwi w natywnym API, na które framework przetłumaczył właściwość, a nie w samej właściwości frameworka. Dokumentację UIAccessibility i AccessibilityNodeInfo warto przeczytać przynajmniej raz; dokumentacja frameworka nabiera sensu dopiero potem.

2

Testuj ogłoszenia na żywo konkretnie na iOS

Asymetria live regionów z sekcji 2 oznacza, że każdy kod zakładający działanie aria-live=“assertive” lub accessibilityLiveRegion=“assertive” będzie po cichu degradował się na iOS. Należy zbudować małe środowisko testowe, które wysyła zarówno grzeczne, jak i asertywne ogłoszenie na obu platformach — z VoiceOver i TalkBack na prawdziwych urządzeniach — zanim wyśle się jakąkolwiek funkcję, której UX zależy od usłyszenia przez użytkownika informacji o zmianie stanu.

3

Przekierowuj przez pomost poza WebViews wszystko, co asertywne

Degradacja asertywnych ogłoszeń w WKWebView nie jest błędem, który Apple wkrótce naprawi — zachowanie jest identyczne w każdym wydaniu iOS od 14 wzwyż. Jeśli dostarcza się aplikację hybrydową, gdzie użytkownik może napotkać krytyczny błąd wewnątrz WebView, należy przekierować ogłoszenie przez pomost JS do hosta i pozwolić, by host uruchomił natywne ogłoszenie platformy. Samo webowe rozwiązanie nie wystarczy.

4

Używaj semantyki „scalenia“ lub „grupowania“ z frameworka, nie dziecko-po-dziecku

Zarówno iOS (shouldGroupAccessibilityChildren), Android (setScreenReaderFocusable), jak i Flutter (MergeSemantics) oferują sposób na zwinięcie wizualnego klastra — ikona plus etykieta plus wartość — w jeden element dostępności. Należy z tego korzystać. Domyślne zachowanie „każdy liść jest fokusowanym elementem“ zamienia sześcioelementowy chip nawigacyjny w sześć gestów przesuwania VoiceOver.

5

Przeprowadzaj audyt z Accessibility Inspector i TalkBack Developer Settings

Obie platformy dostarczają darmowy, oficjalny inspektor drzewa dostępności na żywo — Accessibility Inspector na macOS (połączony z symulatorem lub urządzeniem iOS) oraz nakładkę „Pokaż fokus dostępności“ i „Ustawienia dewelopera“ na Androida. Należy używać ich do odczytu własnego drzewa aplikacji tak, jak widzi je czytnik ekranu; nie należy zakładać, że logi debugowania frameworka pokazują to samo co TalkBack.


Podsumowanie: framework jest na dalszym planie niż platforma

Kuszące jest — a dokumentacja frameworka sprzyja tej pokusie — przekonanie, że wieloplatformowe API dostępności to jednolita abstrakcja nad dwoma równoważnymi natywnymi API. Tabela odwzorowań w sekcji 5 obala tę unifikację. Dwa natywne API zostały zaprojektowane niezależnie, przez dwa różne zespoły, wokół dwóch różnych modeli mentalnych sposobu, w jaki czytnik ekranu powinien przemierzać dokument; różnice są realne, przebijają się przez każdy framework, a wyciek pojawia się w tych częściach doświadczenia użytkownika, które mają największe znaczenie — aktualizacje na żywo, niestandardowe gesty, ukryte poddrzewa, hierarchie nagłówków.

Dobra wiadomość po tym akapicie: podstawy działają. Przycisk z etykietą, obraz z tekstem alternatywnym, nagłówek na początku sekcji — te elementy przechodzą przez każdy framework i ogłaszane są poprawnie na obu platformach. Jeśli dostarcza się wyłącznie te prymitywy, nie trzeba myśleć o UIAccessibility ani AccessibilityNodeInfo; domyślne ustawienia frameworka są uczciwe. Kłopoty zaczynają się, gdy interfejs zaczyna robić coś interesującego — czyli wtedy, gdy dostępność zaczyna mieć największe znaczenie.

Poradnik z sekcji 6 to najkrótsza wersja argumentu, który prowadzi do działającego doświadczenia dla możliwie największej liczby osób z niepełnosprawnościami: myśleć w natywnych prymitywach, testować na prawdziwych urządzeniach obu platform, przekierowywać przez pomost poza WebViews, gdy mamy asertywne zamiary, grupować liście świadomie i korzystać z oficjalnych inspektorów. Wybrany framework pomaga w pierwszych 80 procentach i nie przeszkadza w ostatnich 20. W tych ostatnich 20 procentach żyje użytkownik czytnika ekranu.

„VoiceOver i TalkBack czytają dwa różne dokumenty z tego samego kodu źródłowego. To, czy użytkownik zauważy różnicę, jest miarą tego, jak dobrze rozumie się platformę leżącą pod wybranym frameworkiem.“

— Dział inżynierski Disability World, maj 2026