Dynamic Notifications
This post was co-authored by Chris McMeeking and Jennifer Dailey.
Dynamic content in applications can be tricky to handle properly, and can lead to numerous accessibility violations. It is important to know the tools iOS provides you to handle dynamic content. Dynamic application content is easy to handle properly as long as your understand these tools and how to use them. In this post we will discuss:
- When dynamic notifications should be used
- How to implement dynamic notifications
- Some particularly helpful accessibility notifications and their uses
- An in depth discussion of an example found within our open source Deque University for iOS application
When to Use Dynamic Notifications
When sighted users use an application, they can visually obtain information about on-screen changes. Non-sighted users, however, are denied information about these contextual changes unless developers take special care to communicate these changes. There are many scenarios in which Dynamic Notifications are essential to creating an accessible application. One common example is when a user presses a button. If the button adds or deletes an element (such as the example below), VoiceOver should announce what was added or deleted.
However, be careful not to add too many notifications! It is important to have a good balance. The user should be notified and up-to-date, but not overloaded with audible notifications. Too many notifications can become disruptive and make the app less accessible.
Implementing Dynamic Notifications
To add a VoiceOver notification, simply use the following line of code in a function call:
[code language=”objc”]
UIAccessibilityPostNotification(UIAccessibilityNotification, UIObject);
[/code]
where UIAccessibilityNotification
is one of the many notifications listed UIAccessibility Protocol Reference, and UIObject
is either a UIView
or an NSString
. Each UIAccessibility Notification has a different purpose. The most commonly used ones are listed below.
UIAccessibilityAnnouncementNotification
[code language=”objc”]
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification,
@"Speak this out loud");
[/code]
UIAccessibilityAnnouncementNotifications are useful if you need VoiceOver to read out a custom announcement. In the example above, the notification would prompt VoiceOver to announce the provided string. In practice this should be used sparingly, and should seldom be used as a result of user interaction. Notifications resulting from user interaction should be implemented using the following two notification types:
UIAccessibilityLayoutChangedNotification
[code language=”objc”]
UIAccessibilityPostNotification(UIAccessibilityNotification, UIObject);
[/code]
UIAccessibilityLayoutChangedNotifications are useful when an element has been added or changed and needs to be focused on. This is typically used for changes that take up a small portion of the screen. For example, adding content as a result of a form action. This notification accepts arguments of two types, a UIObject
or an NSString
.
[code language=”objc”]
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification,
@"Speak this out loud");
[/code]
This version will behave exactly like the UIAccessibilityAnnouncementNotification
, which announces the given string. The use cases for this are really the same as for those of the UIAccessibilityAnnouncementNotification
. There is no functional difference between the two, though stylistically one may be more applicable. For example, one might use UIAccessibilityAnnouncementNotifications
to post miscellaneous announcements, while using UIAccessibilityLayoutChangedNotifications
to post announcements specifically when those announcements come as a result of dynamic content being added to the current view.
[code language=”objc”]
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification,
aUIElementObject);
[/code]
The above code would cause VoiceOver to shift focus to the given user interface element, and announce its accessibility label. This is useful for things like custom modal dialogues. Note: To maintain accessibility shifting focus around an application needs to be handled carefully. Overuse of this practice is an easy way to frustrate screen reader users.
UIAccessibilityScreenChangedNotification
UIAccessibilityScreenChangedNotification
is similar to UIAccessibilityLayoutChangedNotification
, with two important distinctions. That is that each variation of the screen changed notification is accompanied by the little “beep-boop” sound that tells a user they are on a different window. The behaviors are roughly the same, except that there is one additional useful variation and this is supplying a nil argument.
[code language=”objc”]
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
[/code]
When you supply a nil second argument, VoiceOver will play the “beep-boop” sound denoting a screen change, shift focus to the first accessibility element on the screen, and speak out its accessibility label. This is the same behavior you would get if you were to do the following.
[code language=”objc”]
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
firstAccessibilityElement);
[/code]
In practice, most of the times you would be tempted to use this notification, the iOS Framework will do the proper thing for you. The times you’d want to do it yourself are very limited. One example would be in the event of custom tabbed navigation. When you change to a new tab, the entire screen doesn’t change, as your tab bar is still there, and the same ViewController still displayed. However, the interaction that has taken place is one that a user would usually associate with the “screen” having changed, but since you’re in the same ViewController iOS doesn’t do the proper thing automatically. In this case, you must post this notification to get the proper behavior, specifically the “beep-boop” sound notifying the user that the screen has changed, and to shift focus to the first accessibility element. OR, if your tab bar is at the top, it may be proper to provide a non-nil argument, that is the first Accessibility element that IS NOT part of your tab bar.
Dynamic Notifications Example
This discussion references our open source Deque University for iOS application. Please get this running on your device and turn VoiceOver on. You can also find Deque University for iOS on the app store, though the version released may be behind the version referenced in the open source repository. The Open Source app is the preferred reference. Before continuing find the “Dynamic Notifications” story, located in the main menu, and navigate to the fixed tab. It is this view that the following discussion refers to.
To a sighted user, this interface is intuitive – tap “Save” to save a contact and tap “Clear Contacts” to delete the entire contents of the list. If the textfield is not empty and “save” is pressed, a sighted user can see that the name was successfully saved, and similarly with “Clear Contacts.” However, what about to a non-sighted user? How can a non-sighted user receive clues about what happened when “Save” or “Clear Contacts” was pressed? When a VoiceOver user taps the “Save” button, by default, VoiceOver only announces “Save.” There is no indication of what was saved or even if the save was successful or not. Similarly, when a VoiceOver user taps the “Clear Contacts” button, VoiceOver only announces “Clear Contacts.” This is where Dynamic Notifications come into play!
After “Clear Contacts” is Pressed
When “Clear Contacts” is pressed, the function (NSString*)clearList
is called. clearList
deletes every contact in the contact list, and calls UIAccessibilityPostNotification
to alert the VoiceOver user whether the list was successfully deleted. The function’s code is below.
[code language=”objc”]
– (NSString*)clearList {
NSString* announcement;
//Creates announcement for if the list was cleared when clearContacts button was pressed.
if([_contactList count] == 0) {
announcement = NSLocalizedString(@"NO_CONTACTS", nil);
} else {
announcement = NSLocalizedString(@"CONTACTS_DELETED", nil);
}
[_contactList removeAllObjects];
[self._tableView reloadData];
[DQUtilities createDynamicNotification:announcement]; // Prompts VoiceOver to announce the change in the list.
return announcement;
}
[/code]
Notice how first, the function takes into account whether the contact list is empty or not. It then creates an announcement with the localized string “NO_CONTACTS” or “CONTACTS_DELETED,” depending on the context. (A localized string is a string placed into the “Localizable.strings” file, so that in case your app gets translated, all programmatically defined strings can be easily found in one file.) “NO_CONTACTS”, in the Localizable.strings file, is defined as “Contact list empty – no contacts deleted,” and “CONTACTS_DELETED” is defined as “Contacts have been deleted.” So, NSString announcement
is assigned to either “Contact list empty – no contacts deleted” or “Contacts have been deleted.” This NSString
then gets passed to the function createDynamicNotification
. Below is the code for createDynamicNotification
.
[code language=”objc”]
+ (void)createDynamicNotification:(NSString*)announcement {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, announcement);
});
}
[/code]
createDynamicNotification
calls UIAccessibilityPostNotification
, which has its first parameter defined as UIAccessibilityAnnouncementNotification
. createDynamicNotification
calls dispatch_time
, a time delay function, to ensure that the notification will be posted by VoiceOver. This way, when “Clear Contacts” is pressed, our notification of “Contacts have been deleted” is not interrupted by VoiceOver’s “Clear Contacts” statement. So, when “Clear Contacts” is pressed, if the contact list is empty, the VoiceOver user will be alerted that no contacts were deleted because the list is empty. If the contact list is not empty, the VoiceOver user will be alerted that contacts were successfully deleted.
After “Save” is Pressed
The “Save” button is implemented in a similar fashion. When “Save” is pressed, the function saveItem
is called, which contains UIAccessibilityPostNotification
. saveItem
checks if the textfield is empty, and if it is, it notifies VoiceOver to post “Textfield empty – no name added to contacts.” If the textfield is not empty, VoiceOver posts “Item was added to contacts,” where Item is the text that was written in the textfield. saveItem
then clears the textfield, removes the keyboard, and adds the contact to the list.
If you would like to see full code for this example, it can be found on Github.
Tips
- Do not use the
UIAccessibilityLayoutChangedNotification
to announce NSStrings, for it may not work. It is known to be buggy. - Have enough notifications to make sure that your users will understand when something changes, but do not have so many such that it clogs VoiceOver with notifications and makes your app unusable.
- When using
UIAccessibilityAnnouncementNotification
, or any notification to announce NSStrings, limit your notification to at most a sentence. Keep it short and informative!
We hope you found this tutorial useful! Check out our Deque University for iOS app for more accessibility tutorials!