weakself

SwiftUI Accessibility (Part 1)

Accessibility is an often overlooked feature that improves the user experience and enables more users to use your app.

Apple highlights how key accessibility is and has created a rich set of APIs and tools to support it. I recommend reading through the guidelines provided by Apple to get a feeling of the big picture.

In this article we are going to take the sneakers concept we built for the SwiftUI letter scroll animation and improve the accessibility of the app. It's a very simple app, but even in such a simple example there is a lot we can improve.

Localisation

It might seem an obvious point, but it's a very important one. Translating the texts in your app to more languages, makes your app more accessible to more users.

In our example we select File > New > File.. and create a new String Catalog. Then we add the language, Spanish in our case, and translate the Strings.

String Catalog

If you would like to go in depth I recommend the documentation provided by Apple.

Dynamic type

Allowing the user to scale up or down the font sizes is also a very important feature. Enabling this option, does not make your app only more accessible, it also leads to a better and more flexible UI overall.

In our example the change is straight forward. Instead of using a fixed font size:

font(.custom("AvenirNextCondensed-Italic", fixedSize: 20))

We use a dynamic size:

font(.custom("AvenirNextCondensed-Italic", size: 20, relativeTo: .body))

That's it. With this change in place the text size adjusts automatically to changes of the dynamicTypeSize environment value.

You can set this value in the Preview to see how your view looks like:

#Preview {
    MyView()
        .environment(\.dynamicTypeSize, .accessibility2)
}

When previewing it you see that the text size increases accordingly, but you also might notice that spacings and paddings stay fixed.

An easy way to improve this is to define a @ScaledMetric value to adjust the padding accordingly:

@ScaledMetric(relativeTo: .body) var paddingScaleFactor = 1

func makeBody(configuration: Configuration) -> some View {
     configuration.label
	     .padding(.vertical, 12 * paddingScaleFactor)
	     .padding(.horizontal, 16 * paddingScaleFactor)
}

To make this cleaner and more reusable we could also create a custom View Modifier:

struct ScaledPaddingModifier: ViewModifier {
    let edges: Edge.Set
    let length: CGFloat?
    @ScaledMetric private var scaleFactor = 1

    private var paddingValue: CGFloat? {
        guard let length else {
            return nil
        }
        return length * scaleFactor
    }

    init(edges: Edge.Set, length: CGFloat?, relativeTo: Font.TextStyle) {
        self.edges = edges
        self.length = length
        self._scaleFactor = ScaledMetric(wrappedValue: 1, relativeTo: relativeTo)
    }

    func body(content: Content) -> some View {
        content
            .padding(edges, paddingValue)
    }
}

Then we define an extension on View

extension View {
    func padding(_ edges: Edge.Set = .all,_ length: CGFloat? = nil, relativeTo: Font.TextStyle) -> some View {
        modifier(ScaledPaddingModifier(edges: edges, length: length, relativeTo: relativeTo))
    }
}

And use it in our View

func makeBody(configuration: Configuration) -> some View {
     configuration.label
	     .padding(.vertical, 12, relativeTo: .body)
	     .padding(.horizontal, 16, relativeTo: .body)
}

Bold texts

The Bold text accessibility setting increases the fontWeight of the texts in your app. In our example project adopting this feature is also straight forward.

We read the legibilityWeight value from the environment

@Environment(\.legibilityWeight) var legibilityWeight

and then set the correct fontWeight depending on the value

configuration.label
	.font(.custom("AvenirNextCondensed-Italic", size: 20, relativeTo: .body))
	.fontWeight(legibilityWeight == .bold ? .bold : .medium)

Like with the dynamicTypeSize, you can preview the result by setting the legibilityWeight environment value

#Preview {
	MyView()
	.environment(\.legibilityWeight, .bold)
}

Increase Contrast

This setting increases the contrast between the app’s foreground and background colors. In our example we are going to do a very subtle change. Depending on if the setting is enabled or not we are going to set a slighly different background color and highlight a bit the cta button.

String Catalog

It's subtle but it's already very effective.

Let's take a look first at the background colors. There are different ways we could approach this. In our example we are going to create a Color Set for each color in our Asset Catalog. And then assign a High Contrast color.

String Catalog
Note: Now Xcode 15 automatically generates Swift symbols for our Color resources.

Then we just have to use this colors and they will adapt if the user enables the Increase Contrast setting.

background(Color(.giannis))

Next we also would like to highlight slighly the CTA button.

First we read the environment value

@Environment(\.colorSchemeContrast) var colorSchemeContrast

And then depending on its value we adjust the opacity of the background.

.background(Rectangle().fill(.white.opacity(
	colorSchemeContrast == .increased ? 0.2 : 0.15
)))

The improvements we have made so far make already a big difference.

But we are not done yet. In the seconds part we take a look at a couple additional relevant accessibility features that are important taking into account.

Tagged with: