Apps für iPhone und iPad für VoiceOver zugänglich machen

In diesem Artikel möchte ich zeigen, wie man eine App, die man für iPhone, iPod Touch und/oder iPad schreibt, so erweitert, dass sie mit Bedienungshilfen wie VoiceOver für Menschen mit Behinderungen ebenfalls nutzbar werden. Immer mehr Anwendungen inklusive Spielen werden mit VoiceOver für Blinde nutzbar, und dieser Artikel kann hoffentlich dazu beitragen, dass es noch mehr werden!

Ein großer Irrtum

Warum sollte ich als Entwickler das überhaupt machen? Blinde können Computer doch eh nicht benutzen, geschweige denn iOS-Geräte mit Touchscreen!

Nun, wer so denkt, könnte falscher nicht liegen! Blinde benutzen Computer seit über 30 Jahren, mit Hilfe von Sprachausgaben, die Bildschirmtext in mehr oder weniger synthetische Sprache umwandeln, und durch Braillezeilen, die Bildschirmtexte in Blindenschrift darstellen. Zu den Betriebssystemen gehören MS-DOS, Windows, GNOME/Linux und natürlich auch die Apple-Betriebssysteme. Im Mobilsektor gab es die ersten zugänglichen Handys unter Symbian S60.

Seit 2005 gehört auch Mac OS X, seit 2009 iOS zu den Betriebssystemen, die ein Sprachausgabesystem für Blinde automatisch und kostenfrei als Bestandteile an Bord haben. Die Funktion heißt VoiceOver und ermöglicht den Zugang zu allen integrierten und sehr sehr vielen herunterlad- und erwerbbaren Apps des App Stores.

Seit iOS 7 wird eine richtige Implementierung der Accessibility-Protokolle auch für Menschen mit motorischen Einschränkungen wichtig. Seit dieser Version des Betriebssystems unterstützt Apple nämlich Switch-Hardware, mit deren Hilfe Menschen, die einen Touchscreen nicht bedienen können, die Benutzeroberfläche dennoch verwenden können.

In iOS 8 sind auch weitere Funktionen der Benutzeroberfläche wie großer text, Kontrasteinstellungen, das Reduzieren der Bewegungen/Animationen und andere Bedienungshilfen aus Apps heraus abfragbar, so dass Sie das Design und Verhalten entsprechend optimieren können, um die Nutzung für einen noch größeren Kundenkreis zu ermöglichen.

Alternative Inhalte unerwünscht!

Wenn Blinde meine Apps nutzen können sollen, muss ich ja alles alternativ und linear anbieten!

Ein weit verbreiteter Mythos ist, dass Blinde alles sequentiell erfassen. Dies ist mitnichten der Fall. Blinde lernen ihre Anwendungen genauso auswendig wie Sehende. Sehende müssen ja den Button zur Erstellung einer neuen E-Mail auch sehr schnell nicht mehr suchen, sondern schieben die Maus automatisch in die richtige Richtung. Genauso lernt ein Blinder entweder die entsprechende Tastenkombination oder – wie im Falle des Touchscreen-Interfaces von iPhone & Co. – die Lage der Buttons und legt den Finger gezielt in die Region.

Weiterhin gibt es auch für VoiceOver-Anwender Techniken, z. B. eine Webseite schnell nach Überschriften, Formularelementen usw. zu durchsuchen, ohne den Text der gesamten Webseite lesen zu müssen. Dies wird durch die sogenannte Rotorsteuerung bewerkstelligt. Ich habe diese im oben verlinkten Erfahrungsbericht genauer beschrieben.

Wichtig ist also, ein Interface so zu gestalten, dass es keine besonderen Elemente für Blinde oder Sehende enthält, sondern dass alle mit denselben Elementen umgehen können.

80% sind wahrscheinlich schon da!

Das ist aber doch bestimmt ganz viel Sonderaufwand!

Nein, ist es nicht. Wird das UIKit verwendet, sind 80% der Zugänglichkeit durch die eingebaute VoiceOver-unterstützung höchstwahrscheinlich schon einfach so geschafft, ohne eine Zeile Code schreiben zu müssen.

Die nächsten ca. 15% werden erreicht, indem man im Interface Builder die richtigen Accessibility Labels vergibt und Accessibility Hints hinzufügt. Auch bis hierhin ist noch keine einzige Zeile Code geschrieben.

Die 100% erreicht man dann durch das Implementieren einiger trivialer Methoden.

Die folgenden Abschnitte sollen zeigen, wie diese einzelnen Schritte aussehen.

Die Arbeit im Interface Builder

Wie oben bereits erwähnt, sind ca. 80% der Zugänglichkeit schon dadurch vorhanden, dass UIKit verwendet wird. Um so grundlegende Dinge wie dass ein Button ein Button ist und ein Switch weiß, wann er eingeschaltet ist, braucht sich also gar nicht mehr gekümmert werden. Der Benutzer hört, wenn er ein anderes UI-Element aktiviert usw. Es geht jetzt also lediglich darum, ein paar Details hinzuzufügen.

Zugängliche Elemente haben drei primäre Zugänglichkeitsmerkmale: Ein Accessibility Label, einen Accessibility Hint und einen oder mehrere Accessibility Traits. Zu beachten ist hierbei, dass das Accessibility Label unabhängig von einem anderen Label zu betrachten ist, den das Steuerelement schon hat. Sollte z. B. ein Button schon eine Textbeschriftung haben, wird VoiceOver diese selbstverständlich verwenden.

Es gibt noch drei weitere Accessibility-Eigenschaften: Accessibility Frame, welcher die Position eines Steuerelements auf dem Bildschirm angibt; Accessibility Value, welcher den aktuellen Wert des Steuerelements als String enthält; und Accessibility Language, die immer dann angegeben wird, wenn das Label von der vom Benutzer gewählten Sprache abweicht. Diese Eigenschaften müssen aber nur dann gesetzt werden, wenn Ihr Steuerelement keine Unterklasse eines Standard-UIKit-Controls oder Views ist. Diese Eigenschaften werden programmatisch festgelegt – dazu später mehr.

Accessibility labels, Hints und Traits werden im Identity Inspector des Interface Builder gesetzt. Es folgt ein Screenshot davon. wenn Sie diesen nicht richtig sehen können, macht das nichts, denn ich werde auf die Eigenschaften gleich eingehen.

Screenshot der Accessibility-Eigenschaften im Interface Builder unter OS X

Die Eigenschaften haben folgende Bedeutungen und werden von VoiceOver folgendermaßen genutzt:

Accessibility Enabled
Dies ist ein Hauptschalter, der festlegt, ob das Element überhaupt zugänglich ist oder nicht. UIViews und alle direkt davon abgeleiteten selbst definierten Unterklassen sind es standardmäßig nicht, UIControls ja. Elemente, die als nicht zugänglich markiert sind, werden von VoiceOver ignoriert und sind vom Benutzer beim Navigieren nicht ansteuerbar.
Accessibility Label
Dies ist eine kurze textuelle Beschriftung für das Element, analog zu dem auf dem Bildschirm sichtbaren Label für einen Button oder anderes Control. Das Label sollte niemals Informationen darüber enthalten, um was für einen Typ von Steuerelement es sich handelt. Dies wird statt dessen durch die Traits festgelegt. Obwohl VoiceOver auf das herkömmliche Label zurückgreift, wenn hier kein Text angegeben wurde, ist es extrem wichtig, ein Label anzugeben, wenn das herkömmliche Label keinen Text enthält (weil es sich z. B. um einen Button mit einem Icon oder Symbol als Label handelt), oder wo durch ein kurzes herkömmliches Label der Zusammenhang für Menschen mit Seheinschränkungen nicht hergestellt werden kann, weil sich der Zusammenhang zum Beispiel aus der Nähe zu anderen Bildschirmelementen ergibt. Ein gutes Beispiel hierfür wäre ein Button „Neu“ oder „+“. Eine gute Beschriftung wäre ein prägnanter Text, ein bis drei Worte lang, der den Sinn des Elements beschreibt. Gute Beispiele sind „Wiedergabe“, „Favoriten“ oder „Neues Ereignis“. Bedenken Sie, dass ein Label zwar kurz sein sollte, dennoch aber den Sinn so beschreiben muss, dass der Zusammenhang für einen blinden Benutzer erkennbar wird. Ein „Neu“-Button macht für Sehende direkt neben einer Liste von Ereignissen durchaus Sinn, für einen Blinden ist „Neues Ereignis“ aber viel aussagekräftiger, weil er den visuellen Zusammenhang zur Ereignisliste nicht so ohne weiteres herstellen kann. In jedem Fall sollte ein Accessibility Label mit einem Großbuchstaben beginnen und am Ende keinen Satzpunkt haben. Dadurch wird VoiceOver ermöglicht, die Beschriftung mit der richtigen Betonung vorzulesen. Labels müssen Übersetzbar sein, in mehrsprachigen Anwendungen also immer in der Landessprache vorliegen, in der auch die Benutzerschnittstelle angezeigt wird.
Accessibility Hint
Diese Eigenschaft beschreibt kurz, was das Element tut. Die Beschreibung sollte nichts enthalten, was bereits durch die Traits vermittelt wird (um welchen Typ von Element es sich handelt, ob es ausgewählt ist o. ä.)). Sie können sich den Hint als eine erweiterte, verdeutlichende Version des Labels vorstellen. Der Hint wird nach einer kurzen Pause gesprochen, nachdem erst das Label und die Traits gesprochen wurden. Hierdurch können Benutzer schnell zum nächsten Element navigieren oder einen Moment abwarten, um den vollständigen Hint vorgelesen zu bekommen. Während Accessibility Labels für jedes Element vorhanden sein sollten, werden Accessibility Hints viel seltener erstellt werden müssen. Stellen Sie nur dann einen Hint zur Verfügung, wenn Ihr Label nicht in allen Situationen selbsterklärend ist. Sie sollten ein Label und einen Hint verwenden, anstatt Ihr Label länger zu machen, denn Labels sollten immer so kurz wie möglich sein. Ein Accessibility Hint sollte immer das Ergebnis der Interaktion mit dem Element beschreiben. Er sollte niemals die Art der Interaktion wie Tippen oder Wischen beschreiben, denn es gibt in VoiceOver verschiedene Möglichkeiten, dasselbe Ziel zu erreichen, und Sie können nie wissen, welche Methode der blinde Benutzer wählt. Hints sollten mit einem Verb in der dritten Person beginnen, wenn dies möglich ist. Zum Beispiel könnte ein Hint „Löscht das Ereignis“ lauten, aber niemals „Lösche das Ereignis“, da dies wie eine Aufforderung oder Instruktion für den Anwender klingt. Hints sollten prägnant sein, ohne dabei auf korrekte Grammatik zu verzichten. Lassen Sie also keine Wörter wie „ein“ oder „das“ weg. Hints sollten mit einem Großbuchstaben beginnen und (anders als bei Labels) mit einem Satzpunkt enden. Sie sollten niemals den Namen oder Typ des Elements enthalten. Sie würden also niemals eine Beschreibung wie „Taste, die das Dokument speichert.“ verwenden, oder „Speichern wird das Dokument speichern.“. Hints müssen übersetzbar sein.
Accessibility Traits
Die Traits beschreiben die Art und das Verhalten des Elements, die bzw. das für die Zugänglichkeit wichtig ist. Sie sollten eine Kombination von Traits wählen, die Ihr Element oder View am besten beschreibt. Die Bedeutungen jedes Traits und welche Auswirkungen diese auf VoiceOver haben, werden unten beschrieben.

Accessibility Traits

Passen Sie die Traits für Ihr Element nur dann an, wenn dies wirklich angebracht ist. Vertrauen Sie auf Ihr Urteil. Traits sind sowohl beschreibend als auch das Verhalten beeinflussend. Wenn ein Trait ein Element besser beschreibt, dann verwenden Sie es. Die wichtigste Frage sollte immer lauten, ob dies eine sinnvolle Ergänzung der Elementbeschreibung für den Anwender ist.

Die Art der Traits kann in zwei Gruppen aufgeteilt werden: Traits für den Typ des Elements, und solche, die das Verhalten beeinflussen. Elementtyp-Traits schließen sich normalerweise gegenseitig aus, es sollte also immer nur eines gesetzt werden, außer es gibt wirklich gute Gründe, dies anders zu machen. Sie können dann einen oder mehrere Verhaltens-Traits verwenden, um Ihr Element tiefgehender zu beschreiben.

Die Elementtyp-Traits sind:

Button
Dieser Trait gibt an, dass das Element ein Button (VoiceOver sagt „Taste“) ist oder sich wie einer verhält.
Link
Dieser Trait gibt an, dass das Element ein Link ist oder sich wie einer verhält. Wenn die Interaktion mit dem Element einen Link aufruft, sollten Sie den Link-Trait dem Button-Trait vorziehen, da dieser spezifischer und daher passender ist.
Search Field
Dieser Trait gibt an, dass das Element ein Suchfeld ist. Es kann erwartet werden, dass der Benutzer durch Eingabe oder Auswahl eine bestimmte Abfrage auslösen kann, die einen Suchvorgang startet und Ergebnisse produziert, aus denen der Benutzer wiederum wählen kann. Dies erscheint auf den ersten Blick sehr spezifisch. Die Suchmöglichkeit ist jedoch für blinde Benutzer sehr wichtig. Ein Suchfeld ist also ein aus Sicht der Zugänglichkeit erstklassiges Element.
Keyboard Key
Dieser Trait gibt an, dass das Element eine Taste einer Tastatur ist oder sich wie eine solche verhält. Dieser Trait ist auf der vom System zur Verfügung gestellten Bildschirmtastatur wie erwartet automatisch gesetzt. Wenn Sie ein Element zur Verfügung stellen, das Auswirkungen auf die Eingabe per Bildschirmtastatur nimmt, sollten Sie diesen Trait setzen. Ein Element, das die verfügbaren Tasten der Bildschirmtastatur umschaltet, wird selbst als Taste einer Tastatur wahrgenommen. Genauso verhält es sich mit Elementen, die vordefinierte Textbausteine einfügen oder die Tastatur von einer alphanumerischen zu einer für eine Formeleingabe umschalten. usw.

Vertrauen Sie auf Ihr Urteil, wenn Sie sich für einen Elementtyp-Trait entscheiden. Versuchen Sie, soviel Beschreibung wie möglich zur Verfügung zu stellen, selbst wenn die Definitionen nicht hundertprozentig übereinstimmen. Verwenden Sie die am meisten übereinstimmende Einstellung, wenn es sich nicht wie „verbiegen“ anfühlt. Die Definitionen sind lose, und die Beschreibungen sollen das Benutzerinteraktionsmodell beschreiben und keine in Stein gemeißelten Vorgaben sein.

Die weiteren Traits beschreiben das Verhalten oder den Zustand eines Elements und können frei kombiniert werden. Diese sind:

Static Text
Dieser Trait zeigt an, dass dieses Element statischen Text enthält, mit dem nicht interagiert werden kann und der sich nicht ändert. VoiceOver bezieht dieses Element dann mit ein, wenn es Teile des Bildschirms vorliest. UILabel-Elemente sind bereits als statischer Text gekennzeichnet. Wenn Sie jedoch eigene Views mit statischem Text verwenden, sollten Sie dies durch diesen Trait angeben.
Image
Dieser Trait zeigt an, dass es sich bei diesem Element um ein Bild handelt. Sie sollten ein Label verwenden, um es korrekt im Zusammenhang zu beschreiben. Wenn man mit diesem Bild interagieren kann, sollten Sie es mit den Button- oder Link-Traits kombinieren.
Plays Sound
Dieser Trait zeigt an, dass dieses Element einen Ton abspielt und sollte mit dem Button-Trait kombiniert werden. Es ist wichtig, diesen Trait solchen Elementen zuzuweisen, die Töne abspielen, damit VoiceOver andere Töne pausieren oder die Lautstärke der Sprechstimme senken kann.
Selected
Dieser Trait zeigt an, dass das Element momentan ausgewählt ist. Dieser Trait wird normalerweise programmatisch umgeschaltet, abhängig davon, in welchem Zustand sich das Element gerade befindet. Dieser Zustand trifft normalerweise auf die aktuell gewählte Zeile einer Tabelle, den aktuellen Abschnitt in einem mehrsegmentigen Steuerelement usw., den Zustand einer Umschalttaste wie „Shift“ auf einer Tastatur usw. zu. Die genaue Bedeutung von „Selected“ ist Ihnen überlassen. Allgemein wird damit jedoch ein aktuell ausgewähltes Element bezeichnet, das sich nicht immer in diesem Zustand befindet, und wo dieser Zustand Auswirkungen auf die Verwendung durch den Benutzer hat.
Summary Element
Dieser Trait gibt an, dass das Element eine Zusammenfassung der aktuellen Situation oder Zustandes beim Start der Anwendung enthält. Ein Download-Manager könnte hierüber z. B. eine Zusammenfassung laufender oder abgeschlossener Downloads vorlesen lassen, die Wetter-App verwendet dies, um die aktuelle Temperatur und Wetterlage des Standorts des Benutzers zu vermitteln (oder je nachdem welcher Ort gerade gewählt ist). Die Inhalte einer solchen Zusammenfassung werden von VoiceOver automatisch vorgelesen, wenn die Anwendung startet. Richtig eingesetzt, ist ein Summary-Trait eine sehr sinnvolle Sache, sollte jedoch sparsam eingesetzt werden!
Updates Frequently
Dieser Trait zeigt an, dass sich das Label oder der Wert eines Elements zu schnell ändern, um von VoiceOver sinnvoll ausgelesen zu werden. Ein gutes Beispiel hierfür sind Fortschrittsbalken oder der Countdown-Zähler der Stoppuhr. VoiceOver wird dieses berücksichtigen und nicht jede Änderung vorlesen, sondern in passenden abständen den Wert selbständig abfragen und vorlesen. Sie sollten diesen Trait auf solchen sich oft verändernden Elementen setzen, um den Benutzer nicht mit Ansagen zu überfluten.
Not Enabled
Dieser Trait gibt an, dass dieses Element zur Zeit nicht verfügbar ist. Natürlich verwendet VoiceOver auch den Status „enabled“ des UIControls, allerdings können Sie hier speziell angeben, dass dieses Element gerade nicht verfügbar ist. Beachten Sie jedoch, dass Sie, wenn ein Element permanent nicht verfügbar ist, die Eigenschaft Accessibility Enabled abschalten sollten, damit VoiceOver dieses Element permanent ignoriert.

Es gibt noch weitere Traits, die nur programmatisch verfügbar sind, aber nicht im Interface Builder:

Starts Media Session
Dieser Trait gibt an, dass das Element eine Media Session startet und VoiceOver nicht sprechen soll. Dies könnte z. B. für den Aufnahme-Button in einer Voice-Memo-Applikation verwendet werden, um VoiceOver mitzuteilen, dass es nicht sprechen soll. Man möchte die Sprachausgabe ja nicht auf der Aufnahme haben.
Adjustable
Gibt an, dass der Wert dieses Elements mit der Wisch-Geste nach oben und unten verändert werden kann. VoiceOver wird ansagen, dass durch wiederholte Aktion der Wert verändert wird. Dies ist z. B. sinnvoll, wenn man es mit Datumsfeldern zu tun hat. Wenn Sie diesen Trait verwenden, müssen Sie zwingend die Eigenschaft AccessibilityValue und die Methoden accessibilityIncrement und accessibilityDecrement implementieren, damit Ihr Element richtig mit VoiceOver funktioniert.
Allows Direct Interaction (iOS 5.0+)
Dieser Trait zeigt an, dass der VoiceOver-Benutzer mit diesem Element direkt interagiert, die Gesten also nicht verändert werden. Dies ist z. B. bei der Klaviatur in GarageBand für iOS der Fall.
Causes Page Turn (iOS 5.0+)
Zeigt an, dass dieses Element Inhalte anzeigt, welche gescrollt werden können. Wenn VoiceOver das Ende der aktuellen Seite erreicht hat, schickt es eine Scroll-Aufforderung an das Element, um die nächste Seite anzuzeigen, und liest automatisch weiter. iBooks zeigt dieses Verhalten zum Beispiel, und genau für diesen Anwendungsfall ist er auch gedacht: Ein Element, das längere Textinhalte wie z. B. Bücher anzeigt. Ab iOS 8 können auch Benutzer einer Blindenschriftanzeige (sog. Braillezeile) davon profitieren, indem sie auch per Weiterlesen-Funktion an ihrem Gerät ein Umblättern und somit ein fortlaufendes Lesen auslösen können und die Hände so niemals zum iOS-Gerät führen müssen.

Wenn Sie die richtige Kombination von Traits verwenden, um Ihr Element zu beschreiben, übernimmt VoiceOver den Rest. Es gibt jedoch Situationen, wo das Setzen oder dynamische Anpassen dieser Traits im Interface Builder nicht ausreicht und man tatsächlich Teile des Accessibility-Protokolls implementieren muss, um die vollständige Zugänglichkeit zu erreichen. Diese werden im folgenden Abschnitt behandelt.

Zugänglichkeit durch Code hinzufügen

Drei häufig auftretende Situationen, in denen das Setzen von Eigenschaften durch den IB nicht ausreichen, sollen hier behandelt werden:

  1. Der Zustand Ihrer Elemente muss sich dynamisch abhängig vom Zustand der Anwendung ändern. Sie könnten z. B. einen Button verwenden, der seine Beschriftung abhängig vom Kontext ändern muss.
  2. Sie haben vielleicht ein eigenes Steuerelement erstellt, dessen geerbte Zugänglichkeitsinformationen nicht aussagekräftig genug sind. Dies kann z. B. eine eigene aufgeteilte View, eine TabView, ein Umschalter mit drei Möglichkeiten oder ein anderes Widget sein.
  3. Sie müssen Sicherstellen, dass ein Benutzer mit Seheinschränkungen über eine Veränderung eines Teils der Benutzerschnittstelle informiert wird, sogar dann, wenn er mit diesem Teil der App im Moment gar nicht interagiert. Es könnte sich z. B. um einen Umschalter handeln, auf dessen Veränderung hin sich anderswo ein View ändert.

Sie können diese Unterstützung einbauen, indem Sie einige einfache Protokolle implementieren und eine Hand voll Funktionsaufrufe tätigen.

Ihre Elemente fallen in eine von drei möglichen Zugänglichkeitskategorien: Nicht zugänglich, zugänglich, oder Container für zugängliche Elemente.

Container sind normalerweise nicht selbst zugänglich, beinhalten aber zugängliche Elemente für den Benutzer. Sie können auch selbst Container, welche zugängliche Elemente enthalten, enthalten usw.

Ein Beispiel hierfür wäre ein selbstdefiniertes Containerelement, welches drei zusammenhängende Buttons enthält, die von der View des Containers verwaltet werden. Die View selbst braucht nicht zugänglich zu sein, die drei interaktiven Elemente, die in ihr enthalten sind, hingegen sollen als selbständige zugängliche Elemente gehandhabt werden. Hierfür ist ein AccessibilityContainer gedacht.

Hierfür bietet UIKit zwei informelle Protokolle: UIAccessibility und UIAccessibilityContainer. Sie bestimmen, welches der beiden Protokolle (oder auch keines) Ihre Elemente implementieren. Alle Standardelemente sind bereits korrekt vorkonfiguriert. Sie können deren Verhalten natürlich durch das Bilden von Unterklassen anpassen.

Das UIAccessibility-Protokoll implementiert lediglich sämtliche Eigenschaften, die in den vorherigen Abschnitten besprochen wurden. Wenn Sie dies von einem UIKit-Element ableiten und die Traits-Methode implementieren, stellen Sie sicher, dass Sie die Traits mit denen der Superklasse per Oder verknüpfen.

Das UIAccessibilityContainer-Protokoll ist da schon etwas interessanter, wenn auch kürzer. Es erlaubt Ihnen die Angabe der Anzahl von tatsächlich zugänglichen Elementen, die dieses Element enthält und die Rückgabe jedes dieser Elemente als eine Instanz der AccessibilityElement-Klasse.

Instanzen von AccessibilityElement haben Eigenschaften wie das Accessibility Label, den Hint, Frame, Value, Trait usw., also die Teile des UIAccessibility-Protokolls. Sie sollten diese für jedes Ihrer Elemente zurückgeben, und VoiceOver erledigt den Rest. Das ist also wirklich sehr simpel!

Schließlich und endlich erlaubt UIAccessibility Ihnen noch, bestimmte Hinweise an den Anwender zu senden, wenn sich etwas in Ihrer App ändert, das anderweitig nicht zugänglich zu machen wäre. Nutzen Sie diese mit Bedacht, um den Anwender nicht zu überfahren!

Testen, testen, testen!

Es geht nichts über das Testen Ihrer Anwendung mit VoiceOver auf iOS-Geräten. Schalten Sie VoiceOver über die Bedienungshilfen, zu finden in Einstellungen, Allgemein, Bedienungshilfen, ein und benutzen Sie Ihre Anwendung so, wie ein Blinder sie nutzen würde, wenn er sie sich aus dem App Store herunterlädt. Im Bildschirm für VoiceOver gibt es einige Hinweise auf die geänderten Gesten, die am Anfang sicher gewöhnungsbedürftig sein werden, aber nach einer kurzen Eingewöhnungszeit sollten Sie schnell merken, ob es in Ihrer Anwendung noch hakt oder ob alles so zugänglich ist, wie Sie es dem Anwender zur Verfügung stellen möchten.

Der iPhone-Simulator in Xcode ermöglicht zwar das Inspizieren der Accessibility-Informationen, wie VoiceOver sie bekommen würde, aber das tatsächliche Ausführen von Gesten und das Hören der Ansagen von VoiceOver kann dieses einfache Debugging-Tool nicht ersetzen.

Sie werden sehr schnell feststellen, ob ein Label auf einem Element fehlt, Elemente, die zugänglich sein sollten, gar nicht auffindbar sind, oder andere Elemente zu finden sind, die vielleicht gar nicht erreichbar sein sollten.

Sie können die hier gemachten Erfahrungen verwenden, um vielleicht noch mehr Benutzerfreundlichkeit in Ihre Apps zu bringen, die natürlich auch sehenden Anwendern zugute kommt!

Ein Beispiel

Das folgende Beispiel soll kurz demonstrieren, wie die verschiedenen oben beschriebenen Techniken im Code aussehen. Es stammt im Original von diesem Artikel: VoiceOver Accessibility vom Blog „Use Your Loaf“.

In diesem Beispiel wird eine simple Aufgabenverwaltung erstellt, deren Grundlage eine Master-Detail-View ist. Es gibt einen Button zum Hinzufügen einer Aufgabe, eine Überschrift und die Tabelle mit den Aufgaben.

Die Überschrift und der Hinzufügen-Button arbeiten ohne weiteres Zutun. Die Tabelle zeigt jedoch im Accessibility-Inspector nur den Namen und die Zeit, die bereits für die Aufgabe verstrichen ist, an, und zwar im Format „0:10“. Man weiß also nicht, handelt es sich um 0 Stunden, 10 Minuten, oder um 0 Minuten und 10 Sekunden, usw. Weiterhin gibt es in dieser Tabelle bei abgeschlossener Aufgabe ein grünes Häkchen, das von VoiceOver bislang komplett ignoriert wird.

Wenn eine Aufgabe erfüllt ist, soll das Label für den Namen der Aufgabe also nun die Tatsache, dass die Aufgabe abgeschlossen ist, enthalten, und die Zeit, die dies gedauert hat, und zwar in einer angenehm verständlichen Sprechweise. Um dies zu erreichen, passt man die Methode configureCell:atIndexPath in UYLTaskListViewController an:

if ([task.complete boolValue])
{
  NSUInteger minutes = [task.duration integerValue] / 60;
  NSUInteger seconds = [task.duration integerValue] % 60;

  cell.detailTextLabel.text = [NSString stringWithFormat:@"%02u:%02u",
                               minutes, seconds];
  cell.imageView.image = [UIImage imageNamed:@"checked.png"];

  NSString *durationText = [NSString stringWithFormat:@"%@ %@",
                            task.note,
                            NSLocalizedString(@"completed in", nil)];
  durationText = [durationText stringByAppendingString:
                               [task.duration stringValueAsTime]];
  cell.accessibilityLabel = durationText;
}

Für die Erzeugung des Textes für die Dauer der Aufgabe wurde eine Kategorie (UYLTimeFormatter) auf der NSNumber-Klasse erstellt, um die Methode stringValueAsTime hinzufügen zu können.

Wählt der Anwender nun eine der Aufgaben aus, wird die Detailansicht angezeigt. Hier kann der Name der Aufgabe geändert werden. Weiterhin gibt es eine Custom View, in der zwei Buttons zum Starten und Stoppen des Timers enthalten sind und die Anzeige der verstrichenen Zeit selbst. Start und Stop sind grafische Buttons. VoiceOver würde zwar, weil die verwendeten Symboldateien start.png und stop.png heißen, halbwegs brauchbare Bezeichnungen selbst zusammenstellen, diese wären aber nicht lokalisierbar.

Das größere Problem ist jedoch der Text der verstrichenen Zeit. Dieser wird durch eine drawAtPoint-Methode direkt auf die View gemalt, so dass kein Accessibility Label oder andere Eigenschaft hierfür gesetzt werden können, um den Text direkt zugänglich zu machen.

Hierfür braucht man also das UIAccessibilityContainer-Protokoll für diese View. Sie zeigt an, dass die View zwei Buttons und einen Text enthält, die zugänglich gemacht werden sollen. Es können hierfür beliebige Rechtecke für diese Objekte angegeben werden. Es handelt sich um ein informelles Protokoll, d. h., man muss nichts in der Klasse UYLCounterView deklarieren um anzuzeigen, dass dieses implementiert wird. Es muss jedoch ein dynamisches Array definiert werden, welches die zugänglichen Elemente enthält. Die folgende Eigenschaft wird der Interface-Definition von UYLCounterView hinzugefügt:

@property (nonatomic, strong) NSMutableArray *accessibleElements;

Es muss also ein UIAccessibilityElement-Objekt für jedes der drei zugänglichen Elemente der View erstellt und konfiguriert werden. Dieses wird in dem Array abgespeichert. Um sich die Arbeit zu sparen, wenn VoiceOver nicht läuft, werden diese auf Anforderung in der Getter-Methode der UYLCounterView-Implementierung angelegt.

Der erste Schritt besteht also darin, im Getter zu überprüfen, ob das Array schon angelegt wurde, und wenn nicht, dieses zu erzeugen:

- (NSArray *)accessibleElements
{
  if (_accessibleElements != nil)
  {
      return_accessibleElements;
  }

  _accessibleElements = [[NSMutableArray alloc] init];

Nun wird für jedes Element ein UIAccessibilityElement-Objekt erstellt und dem Array hinzugefügt. Die Elemente werden in der Reihenfolge der Präsentation von links oben nach rechts unten hinzugefügt. Es wird also mit dem Start-Button angefangen:

  UIAccessibilityElement *startElement = [[UIAccessibilityElement alloc]
                          initWithAccessibilityContainer:self];
  startElement.accessibilityFrame = [self convertRect:self.startButton.frame
                                     toView:nil];
  startElement.accessibilityLabel = NSLocalizedString(@"Start", nil);
  startElement.accessibilityTraits = UIAccessibilityTraitButton;
  if (self.startButton.enabled == NO)
    startElement.accessibilityTraits |= UIAccessibilityTraitNotEnabled;

  [_accessibleElements addObject:startElement];

Einige Hinweise hierzu:

  • Im ersten Schritt wird das UIAccessibilityElement erstellt, und es wird die View angegeben, in der es enthalten ist. Da dies aus der eigenen View heraus geschieht, kann hier self angegeben werden.
  • In der Eigenschaft accessibilityFrame werden die Bildschirmkoordinaten angegeben, die für dieses zugängliche Element verwendet werden sollen. Wichtig hierbei ist, dass eine Konvertierung aus dem View-Koordinatensystem in das System des gesamten Bildschirms stattfinden muss, oder es kommt zu unvorhersehbaren Ergebnissen, besonders dann, wenn der Bildschirm gedreht wird. Das AccessibilityFrame ist also immer auf den Bildschirm bezogen.
  • Sobald das Objekt vorhanden ist, kann das accessibilityLabel gesetzt werden, wie in Standard-UIKit-Elementen.
  • In den Traits muss jetzt angegeben werden, dass es sich um einen Button handelt.
  • Weiterhin muss angegeben werden, ob der Button verfügbar ist oder nicht. Normalerweise macht ein richtiger Button das selbst, aber da hier mit einem selbstdefinierten Element gearbeitet wird, muss der Trait angegeben werden.
  • Letztendlich wird das Element dem Array hinzugefügt.

Als nächstes wird das Element, das den Zählerstand enthält, generiert und hinzugefügt. Die Sache wird durch die verschiedenen Koordinatensysteme des Views und des Bildschirms verkompliziert. Der Code sieht so aus:

CGRect frame = [self convertRect:self.accessibilityFramefromView:nil];

UIAccessibilityElement *counterElement = [[UIAccessibilityElement alloc]
                        initWithAccessibilityContainer:self];
CGRect textFrame = CGRectInset(frame, UYLCOUNTERVIEW_MARGIN + 
                                      self.startButton.bounds.size.width +
                                      UYLCOUNTERVIEW_MARGIN,
                                      UYLCOUNTERVIEW_MARGIN);
counterElement.accessibilityFrame = [self convertRect:textFrame toView:nil];
counterElement.accessibilityLabel = NSLocalizedString(@"Duration", nil);
counterElement.accessibilityValue = [[NSNumber
                                      numberWithInteger:self.secondsCounter]
                                      stringValueAsTime];
counterElement.accessibilityTraits = UIAccessibilityTraitUpdatesFrequently;

[_accessibleElements addObject:counterElement];

Es werden zunächst die View-Koordinaten aus dem Bildschirmkoordinaten errechnet, um die Koordinaten des Textes leichter errechnen zu können, und die Koordinaten des Ergebnisses werden dann wieder in Bildschirmkoordinaten zurückgerechnet, um für die accessibilityFrame-Eigenschaft geeignet zu sein.

Zusätzlich zum accessibilityLabel wird auch die Eigenschaft accessibilityValue ausgefüllt, und zwar mit der verstrichenen Zeit des Zählers als String. Da sich dieser Wert sekündlich ändern kann, wird der Trait UpdatesFrequently gesetzt.

Schlussendlich wird noch der Stop-Button hinzugefügt:

  UIAccessibilityElement *stopElement = [[UIAccessibilityElement alloc]
                                  initWithAccessibilityContainer:self];
  stopElement.accessibilityFrame = [self convertRect:self.stopButton.frame
                                    toView:nil];
  stopElement.accessibilityLabel = NSLocalizedString(@"Stop", nil);
  stopElement.accessibilityTraits = UIAccessibilityTraitButton;
  if (self.stopButton.enabled == NO)
     stopElement.accessibilityTraits |= UIAccessibilityTraitNotEnabled;

  [_accessibleElements addObject:stopElement];

  return_accessibleElements;
}

Sobald dieses Array erstellt wurde, müssen drei sehr einfache Methoden zum Zugriff auf dieses Array definiert werden, die vom UIAccessibilityContainer-Protokoll angefordert werden:

- (NSInteger)accessibilityElementCount
{
  return [[self accessibleElements] count];
}

- (id)accessibilityElementAtIndex:(NSInteger)index
{
    return [[self accessibleElements] objectAtIndex:index];
}

- (NSInteger)indexOfAccessibilityElement:(id)element
{
    return [[self accessibleElements] indexOfObject:element];
}

Um sicherzustellen, dass das UIAccessibilityContainer-Protokoll und die gerade erstellten UIAccessibilityElement-Objekte tatsächlich verwendet werden und die View selbst nicht auf Accessibility-Anfragen reagiert, muss die Methode isAccessibleElement für das selbstdefinierte View implementiert werden und „No“ zurückgeben:

- (BOOL)isAccessibilityElement
{
  return NO;
}

Im nächsten Schritt muss der Wert des UIAccessibilityElements für den Zähler immer dann aktualisiert werden, wenn sich der Text am Bildschirm ändert, also jede Sekunde. In der Setter-Methode für den Text wird dieser Code stehen:

- (void)setSecondsCounter:(NSUInteger)secondsCounter
{
  if (secondsCounter > UYLCOUNTERVIEW_LIMIT)
  {
      secondsCounter = UYLCOUNTERVIEW_LIMIT;
  }

  _secondsCounter = secondsCounter;

  if (_accessibleElements)
  {
      UIAccessibilityElement *counterElement = [self.accessibleElements
        objectAtIndex:UYLCOUNTERVIEW_ELEMENTINDEX_COUNTERTEXT];
      counterElement.accessibilityValue = [[NSNumber
                                          numberWithInteger:secondsCounter]
                                          stringValueAsTime];
  }

  [self setNeedsDisplay];
}

Wichtig ist, dass überprüft wird, ob das Array für die accessibleElements initialisiert wurde und dann per Accessor das Element für den Zählertext geholt und aktualisiert wird.

Die nächste Änderung betrifft die Start- und Stop-Buttons. Wenn der Start-Button gedrückt wird, deaktiviert er sich selbst und aktiviert den Stop-Button. Dies muss auch in den Traits für die beiden Buttons geschehen:

- (void)startAction:(UIButton *)sender
{
  ...
  ...

  if (_accessibleElements)
  {
      UIAccessibilityElement *startElement = [self.accessibleElements
                  objectAtIndex:UYLCOUNTERVIEW_ELEMENTINDEX_STARTBUTTON];
      startElement.accessibilityTraits = UIAccessibilityTraitButton |
                                         UIAccessibilityTraitNotEnabled;

      UIAccessibilityElement *stopElement = [self.accessibleElements
                    objectAtIndex:UYLCOUNTERVIEW_ELEMENTINDEX_STOPBUTTON];
      stopElement.accessibilityTraits = UIAccessibilityTraitButton;        
  }
  ...
  ...
}

Genauso verhält es sich umgekehrt, wenn der Stop-Button gedrückt wird, deaktiviert er sich und aktiviert den start-Button:

- (void)stopAction:(UIButton *)sender
{
  ...

  if (_accessibleElements)
  {
      UIAccessibilityElement *stopElement = [self.accessibleElements
                   objectAtIndex:UYLCOUNTERVIEW_ELEMENTINDEX_STOPBUTTON];
      stopElement.accessibilityTraits = UIAccessibilityTraitButton |
                                        UIAccessibilityTraitNotEnabled;
  }
  ...
}

Ein weiteres Problem besteht, wenn das Gerät gedreht wird. Die Framekoordinaten wurden ja einmal berechnet und werden nicht dynamisch neu berechnet, sobald eine Drehung vom Hoch- ins Querformat oder umgekehrt erfolgt. Mit einem einfachen Trick kann man aber dafür sorgen, dass die accessibilityElements einfach neu erzeugt werden, sobald sie nach einer Drehung des Geräts abgefragt werden. Hierfür implementiert man für den UYLViewTaskController die Methode didRotateFromInterfaceOrientation:

- (void)didRotateFromInterfaceOrientation:
        (UIInterfaceOrientation)fromInterfaceOrientation
{
  self.taskCounterView.accessibleElements = nil;
}

Da oben für den Zählertext ja der Trait UpdatesFrequently gesetzt wurde, kann man sich überlegen, in bestimmten Abständen einen gesprochenen Hinweis abzusetzen. Ein Aktualisieren des Wertes jede Sekunde dauert zu lange, die Sprache braucht länger als eine Sekunde, um den Text zu sprechen, so dass es nicht sinnvoll ist zu versuchen, jeden wert zu sprechen.

Um einen Wert zu sprechen, kann man z. B. diesen Aufruf benutzen:

UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification,
                                @"Hello World");

Der zweite Parameter wird natürlich durch das ersetzt, was tatsächlich gesprochen werden soll. So könnte man in der oben bereits beschriebenen Setter-Methode für den Zählertext eine Überprüfung einbauen, dass jede Minute der Wert einmal zusätzlich an VoiceOver zum Sprechen gesendet wird.

Verbesserte Textdarstellung

Für die Darstellung von Texten wie z. B.Buchseiten bietet iOS ein weiteres, bisher nicht besprochenes Protokoll an, das vielleicht angebrachter sein könnte als UIAccessibilityContainer: UIAccessibilityReadingContent. Dieses Protokoll erlaubt es, mehrzeiligen Text, der nicht editierbar ist, auch für VoiceOver-Anwender so darzustellen, dass sie z. B. mit dem Finger Zeilen „abscannen“ können. Apps, die dieses Protokoll implementieren, sind das Apple-eigene iBooks oder die App Kindle von Amazon. Da auch NSAttributedString verwendet werden kann, kann man auch mehrsprachige Texte von VoiceOver richtig vorlesen lassen, Überschriften als solche kennzeichnen usw.Dieses Beispielprojekt zeigt sehr schön die Implementierung beider Protokolle, und wenn man es compiliert und mit VoiceOver laufen lässt, bemerkt man auch sehr schön die Unterschiede.

Komplexe Grafiken zugänglich machen

Ab iOS 7.0 haben Sie als Entwickler auch die Möglichkeit, komplexe Grafiken wie Karten oder Kurvenverläufe zugänglich zu machen. Beispiele hierfür waren schon in iOS 6 enthalten, nämlich in der Karten- und der Aktien-App. Die API wurde jedoch erst durch die neue Eigenschaft AccessibilityPath des UIAccessibility-Protokolls in iOS 7 für externe Entwickler zugänglich. Ein Beispiel für die Benutzung findet sich in den Codebeispielen zur WWDC 2013, nämlich die Flurkarte des Moscone-Centers in San Francisco in der Beispiel-App zum Vortrag „iOS Accessibility“. Sollten Sie eine solche grafische Zugänglichkeit benötigen, empfehle ich dringend das Studium dieses Programms und das Anschauen des zugehörigen Videos.

Eigene Aktionen auf Elementen zugänglich machen

Es kann vorkommen, dass Sie auf einem Element mehrere Gesten erlauben, wie z. b. das Löschen eines Eintrags durch Wischen nach links, das Markieren o. ä. mit Wischen nach rechts oder ähnliches. Seit iOS 8 ist es möglich, wie oben kurz erwähnt, diese Aktionen auch für Benutzer von VoiceOver zugänglich zu machen. Bedienen Sie sich dazu dem AccessibilityCustomActions-Array aus dem erweiterten UIAccessibilityAction-Protokoll. Jedes Element der Klasse UIAccessibilityCustomAction enthält den lokalisierbaren Namen, das Zielobjekt und eine Selektor-Methode, die die Aktion ausführt. Der Beispielcode des 2014er WWDC-Vortrags zu iOS Accessibility enthält Code, wie dies in der Praxis aussieht. Beispiele finden sich z. B. in Apples eigener Mail App. Durch den Rotor kann man Mails nicht nur löschen, sondern auch als gelesen markieren, sie markieren oder die „Mehr“-Aktion ausführen. Nutzen Sie diese Methode gern anstatt abenteuerlicher Konstrukte wie gesonderter Alert Views für VoiceOver-Nutzer oder Dreifach-Taps mit eben solchen. VoiceOver weist bei Vorhandensein von Aktionen den Nutzer automatisch auf deren Verfügbarkeit hin.

Zugänglichkeit für die Apple Watch

Auch die Apple Watch enthält diverse Bedienungshilfen. Unter anderem ist ein vollständiges VoiceOver an Board, das sehr ähnlichen Regeln folgt wie in iOS-Apps. Die Eigenschaften aus der WKInterfaceObject-Klasse sehen denen von UIAccessibility sehr ähnlich. Es gibt AccessibilityLabel, AccessibilityHint, AccessibilityValue (wo angebracht), AccessibilityTraits und IsAccessibilityElement. Eine Besonderheit bietet lediglich die Eigenschaft AccessibilityImageRegions, welche ein Array von WKAccessibilityImageRegion-Objekten darstellt. Diese repräsentieren gesonderte Bestandteile eines Bildes, das Informationen wiedergibt, die auch VoiceOver-Anwendern gesondert zugänglich sein müssen. Die Klasse selbst folgt üblichen Mustern, und deren Implementierung sollte kein Problem darstellen.

Fazit

Das Hinzufügen von Zugänglichkeitsinformationen ist wirklich einfach. Vieles kann durch Interface Builder gemacht werden, der Rest durch einfach zu implementierenden Code, ohne die Struktur Ihrer Projekte verändern zu müssen. Es ist der richtige Schritt, Ihre Anwendungen zugänglich zu machen, und ich hoffe, dass dieser Artikel Ihnen geholfen hat und Sie die wenigen Extra-Stunden, die dies im Höchstfall beanspruchen wird, investieren werden!

Danksagung

Ein sehr großes Dankeschön geht an Matt Gemmell, dessen Artikel Accessibility for iPhone and iPad apps mir für diesen Artikel als Vorlage für Struktur und Erklärungen diente. Die Veröffentlichung geschah mit seiner ausdrücklichen Genehmigung.

12 Gedanken zu „Apps für iPhone und iPad für VoiceOver zugänglich machen“

  1. Sehr informativer Artikel. Ich kannte schon den Originalartikel von Matt Gemmell und finde es gut, dass sich jemand die Mühe gemacht hat ihn ins Deutsche zu übersetzen. Bei Gelegenheit, werde ich meine Tutorials überprüfen und sicherlich das eine oder andere neue Tutorial verfassen, was sich mit dem Thema VoiceOver beschäftigt.

    1. @Moritz, könnte es daran liegen, dass eine fehlende VoiceOver-Unterstützung blinde Anwender schlicht daran gehindert hat, Deine Apps zu nutzen? 😉

    2. (Woher) Weisst du das so genau? Wenn ich eine App installiere, nehme ich nicht immer Kontakt mit dem Entwickler auf, um ihn über meine Behinderung aufzuklären. Manchmal mach ich’s, manchmal nicht. Vielleicht ist der Kreis deiner „blinden Kundschaft“ grösser als du denkst.

  2. Das war dpfür mich als Laien und Sehbehinderten sehr informativ.
    Drei Fragen:
    Warum gibt es keine Angaben zur Zugänglichkeit bei den Apps?
    Brächte zumindest die Einschaltung der Grundfunktionen etwas für Sehbehinderte, wie das Vorlesen von Texten?
    Warumgibt es keinen Systembaukasten für Sebehinderte, mit Veränderung der typografischen Elemente, Vergrößern von Bedienelementen, der Vergrösserung des im Focus stehenden Elementes, Übertragung der „Reader“-Funktion in Safari z.B. auf Mail oder Zeitungsanwendungen, bzw. reflow wie in goodreader,
    Gruß
    Heinz Mehrlich

  3. Hallo, habe heute das neue OS X Mavericks 10.09. geladen. Seitdem kann ich mit Voiceover nicht mehr navigieren und auch nichts hören, die Sprachausgabe funktioniert nicht (Ton ist selbstverständlich an, Stimme ausgewählt…. habe alles kontrolliert,was mir möglich war.) Hast Du eine Idee, woran das liegen könnte? Bin selbstständig und als Blinder auf die Sprachausgabe angewiesen. MfG Thomas

Was denkst Du darüber?