RWAPP

My name's Rob. I'm an iOS software development engineer for Capital One UK. I talk & write about mobile accessibility.

SwiftUI Accessibility - Traits

Accessibility traits are a group of attributes on a SwiftUI element. They inform assistive technologies how to interact with the element or present it to your customer. Each element has a selection of default traits, but you might need to change these as you create your UI.

In SwiftUI there are two modifiers to use for traits, .accessibility(addTraits: ) and .accessibility(removeTraits: ) which add or remove traits respectively. Each modifier takes as its argument either a single accessibility trait or a set of traits.

1
2
3
Button(action: {}, label: { Text("Button") })
.accessibility(addTraits: [.isSelected, .playsSound])
.accessibility(removeTraits: .isButton)

isButton

This element is a button that you customer can interact with. This causes VoiceOver to announce ‘button‘ after reading the items accessibility label. It also tells Switch Control and Voice Control that it’s possible to interact with this control.

isHeader

Any large text header element that divides content. For example a navigation bar title, or a table section header. This causes VoiceOver to read ‘heading‘ after reading the accessibility label.

By swiping vertically VoiceOver users can skip content and only read elements marked with this trait. This is an essential technique for VoiceOver users, as they can’t visually skim a screen to find the content that’s important to them right now.

isSelected

An item that is currently selected, such as a tab, or an item on a segmented control. By reading ‘selected‘ after the accessibility label this helps VoiceOver users to augment themselves on the screen.

An inline link such as in a webpage. This causes VoiceOver to announce ‘link‘ after reading the item. It also tells Voice Control and Switch Control this element is interactive. Using a rotor setting VoiceOver users can skip content and navigate only elements marked with this trait.

isSearchField

A text field that allows your customer to enter a string to perform a search. This differentiates this field from a standard text field and hints to the user that entering text here should cause the UI to update elsewhere. VoiceOver announces ‘search field‘ after announcing the element’s accessibility label.

isImage

Any image or visual element that has no text and no actions. Image elements set this trait by default, but if you are drawing your own graphics, you may want to set this property. Consider whether it makes sense for this element to be accessible.

playsSound

An element that will trigger sound once activated. This trait tells VoiceOver to stop announcing as soon as your customer activates this element. This avoids conflicting with the sound played.

isKeyboardKey

An item that acts as a key on a keyboard if you’re implementing a custom input control. With this trait on a button, VoiceOver no longer reads ‘button‘ after the accessibility label to allow for quick switching between keys.

isStaticText

Text that does not change throughout the lifecycle of your view. This tells the accessible user interface it doesn’t need to check if the value of this element has changed.

isSummaryElement

A Summary Element trait characterises an area that provides a brief summary of the state of the current screen. The best example of this is Apple’s built in Weather app. On opening a location, VoiceOver highlights the top area, marked as a Summary Element. VoiceOver then reads a quick overview of the current weather conditions in the selected location.

Apple's Weather app with VoiceOver highlighting the top Summary Element.

updatesFrequently

This trait is for elements that update either their label or value frequently. In UIKit you cause use this to tell the accessible user interface to regularly poll this element for changes. Due to the changes in the way SwiftUI generates the AUI, I don’t believe this is still the case. But I am unclear what the purpose of this trait is in SwiftUI.

startsMediaSession

An element that will start playing or recording media when activated. Like playsSound, this trait tells VoiceOver to stop announcing as soon as the user activated the element. This avoids conflicting with the media.

allowsDirectInteraction

Allows Direct Interaction tells VoiceOver there should be no deviation from the standard touch control for this view.

Imagine you have created a music app that provides a piano keyboard for the user to play. Using the VoiceOver paradigm of swiping to key and double tapping would not produce much of a tune. allowsDirectInteraction disables this control allowing your user to play the keyboard by directly tapping the keys. This means your user doesn’t have to disable VoiceOver for the rest of the UI. A game might be a good use for this trait. Inappropriate use of this trait will create a worse experience for your VoiceOver users.

Apple's Garage Band app displaying a piano keyboard

causesPageTurn

This trait indicates to Speak Screen or VoiceOver that this content represents one page out of a set of pages, such as an eBook.

This trait causes the assistive technology to call the closure in your .accessibilityScrollAction() modifier on your parent view immediately after completing reading the content. The assistive technology will then continue reading the new content. Reading will stop if the content does not change after calling this closure, or if you haven’t implemented this modifier. Scroll views implicitly handle the .accessibilityScrollAction() for you. If you want to continue reading in another way, say by transitioning to another screen, or swiping on a carousel, you will need to use this modifier.

isModal

This trait causes assistive technologies to ignore the contents of any other views on screen, allowing access only to the children of this view.


Thanks for reading. This story is part of a series on SwiftUI Accessibility. Check out my other guides in this series:
SwiftUI Accessibility: Named Controls
SwiftUI Accessibility: Images
SwiftUI Accessibility: Dynamic Type
SwiftUI Accessibility: Accessible User Interface
SwiftUI Accessibility: Sort Priority
SwiftUI Accessibility: Attributes

SwiftUI Accessibility - Attributes

When a customer enables an assistive technology to navigate your app the interface that technology navigates isn’t exactly the same as the one visible on the screen. They’re navigating a modified version that iOS creates especially for assistive technology. This is known as the accessibility tree or accessible user interface.

iOS does an incredible job at creating the AUI for you from your SwiftUI code. We can help iOS in creating this by tweaking some element’s accessibility attributes. Setting some accessibility attributes through modifiers is a simple way to add a little more meaning and context for your assistive technology users.

Label

An element’s Accessibility Label is the first string read by VoiceOver when landing on an accessible element. It’s also the string used to activate a control in Voice Control. You should think of this as the name of the element. Set an accessibility label on your SwiftUI element using the modifier .accessibility(label: Text("Send")).

In general, the accessibility label is the same as your control’s label or text value. So that’s what iOS uses by default. This means for most of your elements, you won’t ever need to set an accessibility label. There are a few times when you do need to set one: For example, if you haven’t given your control a text representation. Although the better option here might be to set the text value. If your text value is longer than a couple of words you might want to use a shorter version, this helps with Voice Control. Or if the label might be ambiguous without a little more context.

1
2
Button(action: {}, label: { Text("➡️✉️") })
.accessibility(label: Text("Send"))

A label should allow a VoiceOver user to quickly identify what that element is or does. Not what the content of that element is. Ideally, labels should convey meaning in one word, such as “Play“ or “Like“ for example. Apple advises you should capitalise your accessibility label and don’t end it with a full stop. Don’t include the type of element as this is redundant and will add noise.

Value

This should be the text representation of the value or content of a control. The current numerical value of a slider, or the current status of a switch for example. Typically your accessibility value is defined for you by your control. For example, a slider will always set the accessibility value to its current numerical value.

There are times when you will need to set this value yourself. If you group subviews together into a semantic view, you will need to choose which of your subviews’ values you need to report. You can set a value using the modifier .accessibility(value: Text("10 out of 10")).

At times it may be suitable to set the accessibility value to something different from the value displayed in your visual user interface. Imagine your UI features a slider to adjust, say, the rating of a good dog out of 10. The accessibility value generated for you by the slider will be “100 percent“. It will give your user more meaning if you adjust your sliders accessibility value to read “10 out of 10“.

1
2
Slider(value: $sliderValue, in: minimumValue…maximumvalue)
.accessibility(value: Text("\(Int(sliderValue)) out of 10"))

Don’t get in your customer’s way by adding redundant information. Remember, VoiceOver is not the only medium that uses this label, braille keyboards, for example, will display this value. If you feel you need to add further context for a customer to understand a control, the accessibility label or accessibility hint may be the more suitable attribute to set.

Buttons don’t have an accessibility value by default. But if your text is long, you may be better to set a short accessibility label and the rest of the text as a value. this will help Voice Control users. Imagine a Twitter client that allows users to select a tweet for more options. We would set the text of the tweet as the value, and set the accessibility label to “tweet from @RobRWAPP.

Hint

VoiceOver reads an element’s accessibility hint last after a short pause. Use the hint to give extra information on what the result of performing this element’s action will be. But only if this consequence is not immediately obvious from the element’s accessibility label. Many VoiceOver users disable or skip over hints and only use them if they find an element confusing at first. Because of this, you should use a hint to provide extra context, and not be a required part of your interface.

The hint attributre is optional, and not set for you by iOS. Set an accessibility hint on an element using the modifier .accessibility(hint: Text("Sends your message.")).

In their guidance on writing good accessibility hints, Apple suggests imagining describing the controls action to a friend. You might tell your friend “tapping the send button sends your message”. But assuming you set up your accessibility traits and label correctly, repeating the information that this element is a button and that it’s called “send“ is redundant. So, your hint would be “Sends your message.“ Avoid “Send your message.“ as this sounds like an instruction, rather than guidance. Hints should begin with a capital letter and end with a full stop.

1
2
Button(action: {}, label: { Text("➡️✉️") })
.accessibility(hint: Text("Sends your message."))

Identifier

The connection to accessibility here is a little tenuous. The identifier is not presented to your customer in any way. It is a string you can use to identify your view to UI tests or internally in your app’s code. Set it with the modifier .accessibility(identifier: "My unique identifier"). Where the other attributes take a SwiftUI Text() value, this is not user facing, so takes a Swift String.

1
2
Button(action: {}, label: { Text("➡️✉️") })
.accessibility(identifier: "sendMessageButton")

Thanks for reading. This story is part of a series on SwiftUI Accessibility. Check out my other guides in this series:
SwiftUI Accessibility: Named Controls
SwiftUI Accessibility: Images
SwiftUI Accessibility: Dynamic Type
SwiftUI Accessibility: Accessible User Interface
SwiftUI Accessibility: Sort Priority

SwiftUI Accessibility - Accessible User Interface

Take a look at your app. Notice the collection of buttons, text, images, and other controls you can see and interact with that make up your app’s user interface. When one of your customers navigates your app with Voice Control, Switch Control, VoiceOver, or any other assistive technology, this isn’t the interface they’re using. Instead, iOS creates a version of your interface for assistive technology to use. This interface is generally known as the accessibility tree. Apple often refers to this as your app’s Accessible User Interface. For brevity and consistency in this article, I’ll refer to it as the AUI

Navigating the Weather app with switch control enabled highlights an hour's weather report

Your app’s AUI contains information about what elements are in your visual interface, what order they’re in, and how your users can interact with them. Your customer’s chosen assistive technology will then decide how to use this information. For example, Voice Control and Switch Control help people to interact with your app, so will only access interactive elements. Speak Screen is only concerned with reading content, so will only access elements that aren’t interactable.

Whenever we change an accessibility property, such as changing the sort order, we’re not changing anything on the visual interface. These are direct changes to the AUI for assistive technology to consume.

UIKit

The concept of an accessibility tree, or AUI, is not new to SwiftUI. UIKit has had one since iPhone OS 3, and it’s been on the web since way before that. If you’re a Chrome user, you can see the accessibility tree for this page by visiting chrome://accessibility/. The difference between SwiftUI and UIKit is how iOS creates the AUI. UIKit has a couple of features that mean your AUI can sometimes be less than perfect.

In UIKit iOS builds your visual interface from code or from Interface Builder files. iOS will then generate an AUI from the screen that iOS has drawn. The accessibility API then combines this with any accessibility modifications you have made in code. This step is lossy. The Accessibility API has to make a lot of assumptions about what you intended the experience to be. Apple has done a ton of work in making those assumptions for you, and the majority of them are great. But when you’re creating custom controls or complex UI, iOS won’t always make the right decision.

Additionally, if you change your interface by adding or removing elements without presenting a new screen, the Accessibility API has no way of knowing something has changed. This means you can be presenting elements in your AUI that no longer exist visually, and new visual elements won’t be present in your AUI.

Some fundamental design choices Apple have made in SwiftUI have made a great improvement in these areas. Including an entire class of accessibility bugs that are no longer possible.

Declarative

Our first issue with UIKit is the lossy step of generating an AUI. Because SwiftUI is declarative, we’re separating what we want to display from how we want to display it. Our SwiftUI code containing our Text(), Button(), Image(), and other elements is the what&. The *how is then left to iOS, TVOS, macOS, and WatchOS. This means each platform can make decisions to tailor your interface for itself.

The AUI is just another platform on this list. It can interpret the same code as your visual interface does, and then make a few small decisions about how best to present it in an accessible form. This completely skips the lossy AUI generation step and requires less intervention as a developer.

In Sync

Our second problem with UIKit comes when we change the visual interface without the AUI knowing. It is possible to fix this in UIKit by calling UIAccessibility.post(notification: .layoutChanged) or UIAccessibility.post(notification: .screenChanged) for larger changes. But this requires us as developers to know where these errors are likely to occur and adds dev and testing effort.

With SwiftUI this category of bug is completely eliminated. Because SwiftUI views are structs, and because structs are value types, when some state changes on a SwiftUI view, the struct is re-created. This struct creation triggers the simultaneous redraw of the screen and AUI update. This means your AUI can never be out of sync with what’s visible on the screen.

Diagram of the SwiftUI view creation process.

Both of these improvements require no developer effort aside from using SwiftUI. This makes adopting SwiftUI the simplest and most impactful decision you can make right now to improve accessibility for your customers.


Thanks for reading. This story is part of a series on SwiftUI Accessibility. Check out my other guides in this series:
SwiftUI Accessibility: Named Controls
SwiftUI Accessibility: Sort Priority
SwiftUI Accessibility: Dynamic Type
SwiftUI Accessibility: Images

SwiftUI Accessibility - Sort Priority

Assistive technology, such as VoiceOver, works in natural reading direction. In English, and most other languages, this means top left through to the bottom right. Mostly this is the right decision for assistive technology to make. This is the order anyone not using assistive technology would experience your app. Sometimes though, we make designs that don’t read in this way.
By using the .accessibility(sortPriority: ) modifier we can set the order in which assistive technology accesses elements. To achieve this, you must group elements in a stack (HStack, VStack or ZStack). Then use the .accessibilityElement(children: .contain) modifier. The higher the number we give to .accessibility(sortPriority: ), the earlier VoiceOver will focus on the item. This means an element with a priority of 2 comes before priority 1, and so on.

1
2
3
4
5
6
7
8
9
10
11
12
13
VStack {

Text("Read this last")
.accessibility(sortPriority: 0)

Text("Read this first")
.accessibility(sortPriority: 2)

Text("Read this second")
.accessibility(sortPriority: 1)

}
.accessibilityElement(children: .contain)

One example of using this might be captioning a large image. In SwiftUI images are accessible by default. This doesn’t mean we should focus on the image as the first element - the title is usually more meaningful. Here, we’d set the sort priority of the image to 0 so it receives focus after VoiceOver has read the title and caption.

1
2
3
4
5
6
7
8
9
10
11
12
13
VStack {
Image("shuttle")
.accessibility(sortPriority: 0)

Text("Shuttle")
.font(.largeTitle)
.accessibility(sortPriority: 2)

Text("This is an image of a shuttle on the launch pad")
.accessibility(sortPriority: 1)

}
.accessibilityElement(children: .contain)

Another use of this could be a custom stepper control. We’d want VoiceOver to focus on the value first to orientate your user and inform them which value they’re starting with. Then VoiceOver should follow with the decrease and increase buttons. We’d achieve this like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
HStack {

Button(action: {
self.value -= 1
}) {
Text("Decrease")
}
.accessibility(sortPriority: 1)

Text(String(value))
.accessibility(sortPriority: 2)

Button(action: {
self.value += 1
}) {
Text("Increase")
}

}
.accessibilityElement(children: .contain)

.Contain

As of October 2019, the sort priority only works for elements inside a stack where the stack has the modifier of .accessibilityElement(children: .contain). I don’t believe this is intentional, hopefully, future releases of SwiftUI will drop this requirement.


Thanks for reading. This story is part of a series on SwiftUI Accessibility. Check out my other guides in this series:
SwiftUI Accessibility: Named Controls
SwiftUI Accessibility: Images
SwiftUI Accessibility: Dynamic Type
SwiftUI Accessibility: Accessible User Interface

SwiftUI Accessibility - Named Controls

One big accessibility improvement in SwiftUI comes in the form of named controls. Nearly all controls and some non-interactive views (see Images) can take a Text view as part of their view builder. The purpose of this is to tie the meaning to the control.

1
2
3
Toggle(isOn: $updates) {
Text("Send me updates")
}

Imagine a UIKit layout with a UISwitch control. We’d most likely right align the switch, and provide a text label to the left. Something like this.

Send me updates label left with a switch control right

Visually this makes perfect sense. The control follows natural reading direction from the label so we know they’re connected. This isn’t clear when using some assistive technologies like VoiceOver, Braille keyboards, and Voice Control. For these technologies, there is no link between the separate elements. Remember, VoiceOver users are unlikely to have the benefit of inferring relation by following the visual layout of your UI.

For this UI, VoiceOver will read ‘Send me updates.’ Your user will then swipe, and VoiceOver will read ‘Toggle. Off.’ Your VoiceOver user cannot know you intend a connection. They also have no way of knowing what the consequence of toggling this switch would be. Consider the layout below.

Vertically aligned labels & toggles. The first option is 'Charge me $1m', the second 'send me updates'

The VoiceOver interaction here is exactly the same as above. Because VoiceOver reads in natural direction, both labels will be read before the switches are reached. Your user will hear ‘Send me updates.’ (swipe) ‘Toggle. Off.’ Except if they switch the toggle, we’ll charge them a bunch of money, and we won’t send them the updates they wanted.

Named controls exclude this ambiguity. Because SwiftUI is explicit about the link between the control and the label it can present them as such to assistive technology. This not only provides a clear consequence for activating the control. Grouping also reduces the number of swipes required, making navigation quicker and easier.

These labels also double up as the friendly names used for Voice Control. Without a name for the control, a Voice Control user would have to ask iOS to overlay a grid and say ‘tap 20’. This means unnecessary commands spoken by your customer. Additionally, covering a large proportion of your screen covered with numbers is not a great experience.

Toggle control overlayed by a numbered grid

With a properly named control, the only Voice Control command required to activate the switch is ‘tap Send me updates.’

Toggle control showing the Voice Control command to interact with it

Some elements, like Images, take a Text element as part of their view builder, but it is never displayed on screen. In some instances like Sliders only some platforms such as MacOS display the label. Or you can change the presentation style to include the label. Regardless of how SwiftUI presents your labels (or not), you should always provide a short descriptive label for every control you create. This ensures a better, frustration-free experience for your assistive technology users.


Thanks for reading. This story is part of a series on SwiftUI Accessibility. Check out my other guides in this series:
SwiftUI Accessibility: Images
SwiftUI Accessibility: Sort Priority
SwiftUI Accessibility: Dynamic Type

SwiftUI Accessibility - Dynamic Type

Like all accessibility features, Dynamic Type is about customisability. Many of your customers, and maybe even you, are using Dynamic Type without even considering it an accessibility feature. Dynamic type allows iOS users to set the text to a size that they find comfortable to read. This may mean making it a little larger so it’s easier to read for those of us who haven’t yet accepted we might need glasses. Or it could mean ramping it right up for people with low vision. Or taking the text size down for extra content and privacy.
Like many accessibility features on iOS, Dynamic Type support has been greatly improved in SwiftUI. There are a few things you should do (and not do) to make the most of it.

Do

Nothing

SwiftUI supports dynamic type by default and is multi-line by default. So if you add Text("Some text") to your view, you’re done.

Text Styles

Text is body style by default, which makes it great for the majority of uses. But any app with a single text style is going to look pretty boring. Fortunately, Apple provides a selection of 11 type styles for you to use. Each of these styles supports dynamic type, and adjusts the size, leading and trailing for you as needed. Using too many type styles can lead to your app looking messy and inconsistent. If you find these 11 aren’t enough, it might be worth taking another look at your designs.

Dynamic text sizes

You can choose your required text style using the modifier .font(.headline) to set your text to the headline style. The full list of tyoe styles can be found in Apple’s developer documentation.

Custom Fonts

Apple’s built-in Dynamic Type text styles all use the default San Francisco font. SF is a great font for iOS, but to make your app stand out you need a custom font.
Some devs have told me they don’t support Dynamic Type in their app because it doesn’t support custom fonts. This isn’t the case, Keith Harrison over at Use Your Loaf has a great post on using custom fonts with Dynamic Type in UIKit that I highly recommend.
Unfortunately, these methods won’t work for us in SwiftUI. Custom font support for dynamic type in SwiftUI needs some improvement from the current version. There is however, a simple way we can leverage some of the built-in text style’s dynamic type support. By adding a helper method, we can get the current point size for our desired text style.

1
2
3
func textSize(textStyle: UIFont.TextStyle) -> CGFloat {
return UIFont.preferredFont(forTextStyle: textStyle).pointSize
}

Then we can use the custom font modifier to apply this to our text.

1
.font(.custom("MyCustomFont", size: textSize(textStyle: .headline)))

Custom font

The downside of this approach is this fixes the text size until the screen is redrawn. So if your customer changes their dynamic type size your text won’t change until this screen is recreated.

Don’t

Line Limit

It’s possible to limit the number of lines your text wraps to using the modifier .lineLimit(1). Meaning if your text requires more lines it will end in an ellipsis. This is a poor choice as your users with the largest text sizes are likely to lose the full meaning. As a rule, your UI should be able to accommodate whatever text content at whatever size its provided with. If you find your screen or control can’t handle this, it’s worth taking another look at your design, or how you’ve built it.

Truncated text

Fixed sizes

There are two ways of fixing a font size. .font(.system(size: 17)) or .font(.custom("MyCustomFont", size: 17)). please don’t be tempted to use these. If your customer has chosen to set their preferred text size, either for accessibility reasons, or because they like it that way, it’s pretty arrogant as an app developer to ignore this – at least, that’s how your customer will see it.

Fixed text size

As with the line limit, if you find your screen or control doesn’t work with larger fonts re-visit the design or how you’ve built it. Sometimes it’s not possible to support larger text sizes for certain controls. See iOS’ standard tab bar for example. In these situations, we can add a function that calls our dynamic size function above, and provides a maximum value. This allows your text to scale down, but limits how large it can become.

1
2
3
func textSizeForThisOneSpecificUse() -> CGFloat {
return fmin(textSize(textStyle: .body), 28.0)
}

Thanks for reading. This story is part of a series on SwiftUI Accessibility. Check out my other guides in this series:
SwiftUI Accessibility: Named Controls
SwiftUI Accessibility: Images
SwiftUI Accessibility: Sort Priority

SwiftUI Accessibility - Images

Images in SwiftUI are accessible by default. This is the opposite of what we’d experience in UIKit, where images are not accessible unless you set isAccessibilityElement to true.

Sometimes making images not accessible to VoiceOver is the right decision. Like when using a glyph as a redundant way of conveying meaning alongside text. An example of this would be displaying a warning triangle next to the text ‘Error’ or a tick next to ‘success’. If these images were accessible your VoiceOver users would hear the word ‘error’ twice and have to swipe between each one. This makes navigation longer and frustrating for your customer.

error message

If images are a large part of your UI then making them not accessible can be confusing and frustrating to VoiceOver customers. Low vision users will often still be able to tell there is content on the screen, and will attempt to discover it by moving their finger over the area. If the image is not accessible to VoiceOver all your user will hear is an irritating ‘dunk’. This can lead VoiceOver users to assume your app is not accessible at all, and is a big turn-off.

Making images accessible by default helps to provide a more comparable experience for assistive technology users. But the main reason Apple have made this change is because of a new iOS 13 feature. VoiceOver in iOS 13 will now use CoreML to determine the content of your image and will describe the image to your VoiceOver user.

Because of this change, there are a couple of considerations you need to bear in mind when coding images in SwiftUI.

Image names

Because your image is accessible, VoiceOver needs some content to announce. Most of the time the only readable content you provide is the file name of the image.

1
Image("shuttle")

This is fine if you call your image image Space shuttle, but if your image is called 164234main_image_feature_713_ys_full then what your customer hears is useless and frustrating. Image, like most SwiftUI elements, can take a Text value providing a friendlier string to read to your user.

1
Image("164234main_image_feature_713_ys_full", label: Text("Shuttle"))

Decorative images

Sometimes it’s not appropriate for an image to be accessible. For example, providing an error icon next to the text ‘error’. If this image was accessible your VoiceOver user would hear ‘Error’ (swipe) ‘Error’. This duplication adds time and effort in navigation for VoiceOver users. In this instance it’s better to use the Image initializer decorative, this will display your image the same as above, but it is now hidden to VoiceOver.

1
Image(decorative: "Error")

error message

System Images

System images are a great new feature in iOS 13, Apple provides a suite of common system glyphs such as info circles and share icons that you can use as icons in your app. You can browse the full collection by downloading the SF Symbols app from the Apple Developer website.
In my opinion, Apple has implemented the accessibility wrong for these images. Like all images in SwiftUI, these system glyphs are accessible by default. As these are system images they have names like ‘keyboard.chevron.compact.down’ and ‘questionmark.video.fill’. These are the names that VoiceOver will read and are meaningless to your customers. Apple should mark these images as not accessible by default, or at least have an initializer option to add a friendly name. Unless and until Apple makes a change like this, you will need to add the .accessibility(hidden: true) modifier to any system images you are using.

1
2
Image(systemName: "exclamationmark.triangle.fill")
.accessibility(hidden: true)

Thanks for reading. This story is part of a series on SwiftUI Accessibility. Check out my other guides in this series:
SwiftUI Accessibility: Named Controls
SwiftUI Accessibility: Sort Priority
SwiftUI Accessibility: Dynamic Type

What the European Accessibility Act (Might) Mean for Mobile Development

The European Accessibility Act, or EAA is due to become law in Europe later this year, and it defines some specific requirements for mobile. In fact, its the first accessibility legislation that I’m aware of, anywhere, that explicitly covers mobile apps.


Since 2012 the European Union has been working on standardising accessibility legislation across Europe. The ultimate aim is to both improve the experience for those who need to use assistive technology, but also to simplify the rules business need to follow on accessibility. The years of discussions and consultations has lead to the European Accessibility Act, written in 2018, which covers a range of requirements for, amongst other channels, mobile.
While the EAA doesn’t blanket cover every category of app, the net is pretty broad. The act covers any apps sold or in use within the European Union that fall into these categories, so even if you’re not part of the Euro Zone if your app is available on an app store any country that is, the act applies to you. So, if you’re in the business of making mobile interactions, you’ll need to be prepared. Fortunately, both Apple and Android provide many of the tools required to conform to the law at a system level, meaning you’ll likely be a long way towards complying already.
As with any new law, until tested in the courts, it is somewhat open to interpretation. Also, I’m not a lawyer, I’m a mobile developer with a keen focus on accessibility, so this post makes up my personal thoughts about how I would try to comply with the law and will undoubtedly contain factual inaccuracies. I’ve not included everything but focussed on the areas I think are most useful to mobile developers. If you believe this new legislation may regulate your app, I’d highly recommend reading the act through yourself, and of course, you’ll need to get some legitimate legal advice.

Apps Types Covered

The EAA specifically covers mobile apps in certain areas, while this doesn’t cover all apps, it’s safe to say these categories make up a significant section of mobile apps available.

Transport

Any app related to publicly available transport including air, bus, rail and ‘waterborne’ transport. The act doesn’t specify taxis or ride sharing specifically, but arguably they could be included under the definition of ‘passenger transport services’

Banking

Any app that provides banking services — the language for this category uses the phrase ‘banking services’ specifically, suggesting the EU would apply this rule not only to banks themselves but more broadly to apps that use open banking to access other accounts or other banking related services.

e-commerce

Any app that allows for digitally purchasing either digital or physical goods or services. This clause will be the one that covers the majority of apps, and the requirements here are not as detailed as the more specific domains above.

Requirements for All Apps & Websites

Alternatives to Non-Text Content

If you present content in video form, make sure your video features subtitles or closed captioning, provide a transcript for any content presented in video or audio format. If you present text content in an image or as part of a video, add an accessibility label or subtitles that can be read by a screen reader.

Make Content Available to Screen Readers

This requirement covers two points from the act. Firstly:

Information content shall be available in text formats that can be used to generate alternative assistive formats to be presented in different ways by the users and via more than one sensory channel.

Also:

consistently … [present content] in a way which facilitates interoperability with a variety of user agents and assistive technologies.

My understanding of these paragraphs is that any text-based content should be accessible to screen readers (VoiceOver and TalkBack). Screen reader users make up by far the most significant constituency of assistive technology users. Also, both OSes also use the same techniques they use to support screen readers to allow support for other assistive technologies, so get screen readers right, and you’ll find other assistive technology will work well too.
For the most part, both Android & iOS built-in screen readers will do a great job of making the text available for the user, even making guesses at text included in images where needed. However, at times it can be easy to cause unexpected behaviour from screen readers — from elements being out of order to elements being missed entirely or read when not present on the screen. The best way to see if you need to make any changes here is to test with your devices screen reader enabled.

Requirements for Transport & Banking Apps & Websites

Flexible Magnification

It is unclear from the act whether magnification applies to simple screen magnification or to allowing magnification of text sizes. I suspect it would be relatively simple to argue that screen magnification fulfils this requirement if you’re looking to satisfy the minimum needed by the law. Screen magnification, however, isn’t a great experience for your customer, as it cuts context and reduces discoverability, so I’d highly recommend supporting dynamic text sizes regardless of the true meaning of the law.
Screen magnification is a system feature available on both platforms (Android, iOS) that requires no developer changes. Dynamic text sizes, however, need a little more consideration. It’s worth giving some UX thought to how a screen will look and function when the screen appears with the largest text, you will at times need to make decisions on compromises to design with accessibility text sizes.
iOS Developers will need to adopt the use of iOS’ built-in text styles and use the adjustsFontForContentSizeCategory property for each content label.
For Android, you should use SP sizes for text that scale from your standard size to match your customer’s device settings.

Flexible Contrast

In regular use, your text to background contrast ratio should be 4.5:1 for most text. With this setting enabled, you should look to have a contrast ratio of 7:1, so this might require some UX decisions as to what colours to use.
iOS provides an accessibility setting to increase contrast, as a developer, you can listen to UIAccessibility.isDarkerSystemColorsEnabled to see if you need to make UI changes based on this setting. I’d also highly recommend listening to UIAccessibility.isReduceTransparencyEnabled if you’re making use of transparency or blur in your app, and if the user has this setting enabled, be sure to provide an alternative. The alternative doesn’t have to be a solid colour, a reduced alpha or increased blur may be enough. A technique Apple make use of on SpringBoard is to use a solid colour that has a tint of the colour underneath.

Springboard
iOS SpringBoard with standard settings

Springboard
iOS SpringBoard with reduced transparency enabled
Android does provide a High Contrast Text accessibility setting, but unfortunately provides no developer documentation regarding what this does, or how developers can leverage it, so this may require a setting within your app to increase contrast.

Alternative Colour

Alternative colour is potentially the caveat that may need the most work to conform to; this probably needs someone more skilled than me in reading legislation to precisely know what this clause means.
It’s possible that the customer’s device’s built-in colour filters will fulfil this requirement, as inverting colours (Android, iOS), switching to greyscale, or adding a filter (Android, iOS) to the screen would have this effect. However, it could mean that you are expected to allow a setting for your customers to change your main body text colour. I’m not aware of a system framework on either platform to allow for simple switching of body text colours in this way, so may require some global skinning work for your app, and possibly some UX decisions.
The text says only ‘provide for an alternative colour to convey information,’ I’ll let you draw your own conclusion as to which option would allow your app to fulfil the requirement.

Alternative to Fine Motor Control

Both iOS and Android support control by external switch devices, or by using the device’s screen or buttons as a switch. Providing your app works as expected with screen readers, you’ll probably find switch control works fine, but it’s worth testing with this enabled.

Be More Like Neil

Toby Jones (as Neil) and Neil Baldwin
Toby Jones (as Neil) and Neil Baldwin

Like many, perhaps even all, of us in software engineering, and I’m sure other disciplines as well, I suffer from imposter syndrome. A crushing sense that I don’t, and never will, have the skills and knowledge to match my colleagues. I live in constant fear of being found out, that one day someone will come up to my desk and tell me they’ve figured out that I’ve been lying about my abilities all along, and that I need to pack my things and leave the building immediately. Its a feeling that caused me to take almost 10 years from writing my first code to even considering applying for a job in software, and when I first got a call from the recruiter, I genuinely thought she’d made a mistake and picked the wrong CV pile.

In reality, software is a field so incredibly vast and complex that no-one could ever know or understand everything, or even most things. We’re all learning and improving our skills all the time, and we all know different things as a result of our different experiences.

This weekend I was honoured to have the chance to meet a man who has been an inspiration to me for a few years now. A man for whom feeling he doesn’t belong has never caused him to question his abilities, perhaps quite the opposite - causing him to make every opportunity he could to do the things that made him happy. Someone who’s probably well known to you if you’re a 1990’s English league footballer, clown, or Church of England Bishop - a man named Neil Baldwin. I’m not the only person to be inspired by Neil, his friend, England’s best ever World Cup goal scorer Gary Lineker said of Neil “you want to be like him”. I think being more like Neil is a noble aim, and one we could probably all benefit from.

“I saw the Queen last week” Neil told me when I met him, in the same way you might mention to a friend you’d seen an old mutual acquaintance. I later found out he hadn’t just seen the Queen, but had been invited to Buckingham Palace to be presented with the British Empire Medal by Her Majesty, a medal he had pinned to his chest when we chatted, along with a small, seemingly random, selection of charity and football league lapel pins he had scattered on his jacket. This sums up Neil, he’s achieved a lot in his storied life, much of it is documented in the multi-BAFTA winning biopic Marvellous. Much of the rest you’ll find in his autobiography, standing out amongst his co-writer’s other titles, almost exclusively biographies of British Prime Ministers. All of those achievements are important to Neil, and he’s rightly proud, but none of them more important than any other.

Neil accepting a BAFTA for Marvellous
Neil accepting a BAFTA for Marvellous

Neil left school at 16, he was diagnosed with learning difficulties, and although he had no problem with school, it didn’t make him happy. Neil had made a simple decision, one we should probably all make, and more importantly remind ourselves of - that he wanted to be happy. Given that credo, there was a clear next step for Neil’s career - he joined the circus to become a clown. After a few years of falling of the back of a fire truck to make others laugh, Neil returned home to his family. Unemployed, Neil’s career goal was to manage Stoke City Football Club. So Neil made sure he was in the right place at the right time, and was invited to be Stoke City’s kit man. Stoke manager, Lou Macari who hired Neil directly described this as “the best signing I ever made”

Neil interrupting a TV interview with Stoke City manager Lou Macari
Neil interrupting a TV interview with Stoke City manager Lou Macari

Neil’s friends include Prince Edward - Neil just “knocked on his door” and introduced himself - the Archbishop of Canterbury - each one since the 60’s - and former England manager Kevin Keegan. He has an honorary degree from Keele University, and even played a match for his beloved Stoke City. Asked bout his learning difficulties, Neil replies “what difficulties?” Asked about what he’s achieved, Neil says: “I hope it shows people that you can do what you want with your life, if you keep at it.”

Neil accepting his degree from Keele University
Neil accepting his degree from Keele University

It’s natural to doubt yourself and your abilities, but there are a few lessons I think we can all take from Neil to help us remember we’re all capable of achieving the best we can.

  • Be happy
  • Make other people happy
  • Be confident in your own ability to do what makes you happy
  • Be proud of all your achievements, but don’t brag
  • Make your own opportunities

Perhaps being a little more like Neil might make us all happier.

Mars Watch Privacy Policy

Privacy Policy

Rob Whitaker built the Mars Watch app as an Open Source app. This SERVICE is provided by Rob Whitaker at no cost and is intended for use as is.

This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.

If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.

The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Mars Watch unless otherwise defined in this Privacy Policy.

Information Collection and Use

For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information, including but not limited to email address & name if you choose to contact me.

The app does use third party services that may collect information used to identify you.

Link to privacy policy of third party service providers used by the app

Log Data

I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics.

Cookies

Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device’s internal memory.

This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.

Service Providers

I may employ third-party companies and individuals due to the following reasons:

  • To facilitate our Service;
  • To provide the Service on our behalf;
  • To perform Service-related services; or
  • To assist us in analyzing how our Service is used.

I want to inform users of this Service that these third parties have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.

Security

I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security.

Links to Other Sites

This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.

Children’s Privacy

These Services do not address anyone under the age of 13. I do not knowingly collect personally identifiable information from children under 13. In the case I discover that a child under 13 has provided me with personal information, I immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact me so that I will be able to do necessary actions.

Changes to This Privacy Policy

I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately after they are posted on this page.

Contact Us

If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me, rw@rwapp.co.uk.

This privacy policy page was created at privacypolicytemplate.net and modified/generated by App Privacy Policy Generator

Terms & Conditions

By downloading or using the app, these terms will automatically apply to you – you should make sure therefore that you read them carefully before using the app. You’re not allowed to copy, or modify the app, any part of the app, or our trademarks in any way. You’re not allowed to attempt to extract the source code of the app, and you also shouldn’t try to translate the app into other languages, or make derivative versions. The app itself, and all the trade marks, copyright, database rights and other intellectual property rights related to it, still belong to Rob Whitaker.

Rob Whitaker is committed to ensuring that the app is as useful and efficient as possible. For that reason, we reserve the right to make changes to the app or to charge for its services, at any time and for any reason. We will never charge you for the app or its services without making it very clear to you exactly what you’re paying for.

The Mars Watch app stores and processes personal data that you have provided to us, in order to provide my Service. It’s your responsibility to keep your phone and access to the app secure. We therefore recommend that you do not jailbreak or root your phone, which is the process of removing software restrictions and limitations imposed by the official operating system of your device. It could make your phone vulnerable to malware/viruses/malicious programs, compromise your phone’s security features and it could mean that the Mars Watch app won’t work properly or at all.

You should be aware that there are certain things that Rob Whitaker will not take responsibility for. Certain functions of the app will require the app to have an active internet connection. The connection can be Wi-Fi, or provided by your mobile network provider, but Rob Whitaker cannot take responsibility for the app not working at full functionality if you don’t have access to Wi-Fi, and you don’t have any of your data allowance left.

If you’re using the app outside of an area with Wi-Fi, you should remember that your terms of the agreement with your mobile network provider will still apply. As a result, you may be charged by your mobile provider for the cost of data for the duration of the connection while accessing the app, or other third party charges. In using the app, you’re accepting responsibility for any such charges, including roaming data charges if you use the app outside of your home territory (i.e. region or country) without turning off data roaming. If you are not the bill payer for the device on which you’re using the app, please be aware that we assume that you have received permission from the bill payer for using the app.

Along the same lines, Rob Whitaker cannot always take responsibility for the way you use the app i.e. You need to make sure that your device stays charged – if it runs out of battery and you can’t turn it on to avail the Service, Rob Whitaker cannot accept responsibility.

With respect to Rob Whitaker’s responsibility for your use of the app, when you’re using the app, it’s important to bear in mind that although we endeavour to ensure that it is updated and correct at all times, we do rely on third parties to provide information to us so that we can make it available to you. Rob Whitaker accepts no liability for any loss, direct or indirect, you experience as a result of relying wholly on this functionality of the app.

At some point, we may wish to update the app. The app is currently available on iOS – the requirements for system(and for any additional systems we decide to extend the availability of the app to) may change, and you’ll need to download the updates if you want to keep using the app. Rob Whitaker does not promise that it will always update the app so that it is relevant to you and/or works with the iOS version that you have installed on your device. However, you promise to always accept updates to the application when offered to you, We may also wish to stop providing the app, and may terminate use of it at any time without giving notice of termination to you. Unless we tell you otherwise, upon any termination, (a) the rights and licenses granted to you in these terms will end; (b) you must stop using the app, and (if needed) delete it from your device.

Changes to This Terms and Conditions

I may update our Terms and Conditions from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Terms and Conditions on this page. These changes are effective immediately after they are posted on this page.

Contact Us

If you have any questions or suggestions about my Terms and Conditions, do not hesitate to contact me, rw@rwapp.co.uk.

This Terms and Conditions page was generated by App Privacy Policy Generator