Reduce Motion: How To Make Your iOS App Animations Accessible and Inclusive
Introduction
When creating iOS animations for your apps, you should always consider the needs of people who may be sensitive to excessive motion and screen effects. Luckily, you can implement the reduce motion feature in your app to allow users to turn off screen movement. This article covers “reduce motion” implementation in real iOS apps. You will also learn how to detect and implement the feature for SwiftUI animations.
Getting Started
The completed Xcode sample project is on GitHub. You can download, explore and test the reduce motion feature using the simulator.
What is Reduce Motion?
Reduce Motion is a feature in iOS that allows users to disable motion effects and excessive animations. You can find it in the accessibility settings on iOS. To switch it on, you can go to Settings > Accessibility > Reduce Motion.
How Does Reduce Motion Work?
When on, some screen effects and animations will be disabled. Reduce motion does not disable all animations by default, so you should implement it in your app if it involves large and complex animations. It helps you make your app accessible and inclusive.
Excellent Usage of Reduce Motion on iOS
How To Make iOS Animations Accessible to Screen Readers and Voiceovers
In this section, you will learn about how to make your iOS animations accessible and inclusive using the basic accessibility modifiers available in SwiftUI. To use the hamburger menu icon in the image below, you can find it in the Xcode project. You can also use your asset to follow along.
The icon above transitions into a close icon using a tap gesture. Begin by creating a new SwiftUI project with Xcode and name it ReduceMotionSpring.swift. In the declaration section, add the following animation variables. They will be used for animating the icon.
@State private var isRotating = false
@State private var isHidden = false
let subtleFeel = Animation.easeInOut // Use when Reduce Motion
is On
let bouncyFeel = Animation.interpolatingSpring(stiffness: 300,
damping: 15)
// Detect and respond to reduce motion
@Environment(\.accessibilityReduceMotion) var reduceMotion
In the body of some View, add three rectangles inside a VStack container.
VStack(spacing: 14) {
Rectangle() // top
.frame(width: 64, height: 10)
.cornerRadius(4)
.rotationEffect(.degrees(isRotating ? 48 : 0), anchor:
.leading)
Rectangle() // middle
.frame(width: 64, height: 10)
.cornerRadius(4)
.scaleEffect(isHidden ? 0 : 1, anchor: isHidden ?
.trailing: .leading)
.opacity(isHidden ? 0 : 1)
Rectangle() // bottom
.frame(width: 64, height: 10)
.cornerRadius(4)
.rotationEffect(.degrees(isRotating ? -48 : 0), anchor:
.leading)
}
The code above draws the hamburger icon in the preview. Don’t worry about the parameters isRotating and isHidden for now. They are used for showing and dismissing the animation when you tap the icon.
When the iOS screen reader steps through the code above, it will read “Rectangle”, “Rectangle”, “Rectangle” because there are three separate rectangular elements that form the icon. Also, the iOS voiceover has no idea whether this is a button or not. Luckily, SwiftUI provides you with basic accessibility modifiers to make this icon accessible.
First, you should tell the iOS voiceover that this icon is actually a combined element by attaching .accessibilityElement(children: .combine) to the VStack containing all three rectangles. Next, inform the voiceover that this is actually a button by adding .accessibilityAddTraits(.isButton) to the VStack. Finally, add a descriptive label for the button using .accessibilityLabel("Hamburger menu").
Recommended by LinkedIn
How to Detect and Respond to User’s Reduce Motion Accessibility Settings
In SwiftUI, the @Environment property wrapper provides you access to “reduce motion”. You have already defined this sample code previously. So, add the animation to the icon.
// Detect and respond to reduce motion
@Environment(\.accessibilityReduceMotion) var reduceMotion
To animate the menu icon, you should change it over time using the state variables isRotating and isHidden you already declared. You will use gestural interaction to trigger the animation. So, add the onTap gesture to the VStack parent container and set the final states of the animation in the onTap gesture closure.
VStack(spacing: 14) {
Rectangle() // top
.frame(width: 64, height: 10)
.cornerRadius(4)
.rotationEffect(.degrees(isRotating ? 48 : 0), anchor:
.leading)
Rectangle() // middle
.frame(width: 64, height: 10)
.cornerRadius(4)
.scaleEffect(isHidden ? 0 : 1, anchor: isHidden ?
.trailing: .leading)
.opacity(isHidden ? 0 : 1)
Rectangle() // bottom
.frame(width: 64, height: 10)
.cornerRadius(4)
.rotationEffect(.degrees(isRotating ? -48 : 0), anchor:
.leading)
}
.accessibilityElement(children: .combine)
.accessibilityAddTraits(.isButton)
.accessibilityLabel("Hamburger menu")
.onTapGesture {
withAnimation(bouncyFeel){
isRotating.toggle()
isHidden.toggle()
}
}
The code above creates a bouncy spring animation that overshoots the resting states of the “menu” and “cancel” icons.
How to Substitute an Excessive Animation With a Subtle One When Reduce Motion is On
Most people have sensitivity to motion and excessive screen effects on iOS. Therefore, when you create iOS app animations, make sure to provide the ability to switch off your animations when “reduce motion” is on. Previously, you declared the variable reduceMotion.
// Detect and respond to reduce motion @Environment(\.accessibilityReduceMotion) var reduceMotion
It is now time to use it to disable the springy/bouncy animation. You can implement it using ternary conditional operation along with the variable reduceMotion. In the parenthesis of withAnimation, replace bouncyFeel with reduceMotion ? subtleFeel : bouncyFeel.
.onTapGesture {
withAnimation(reduceMotion ? subtleFeel : bouncyFeel){
isRotating.toggle()
isHidden.toggle()
}
}
The code above replaces the bouncy spring animation with the subtle animation let subtleFeel = Animation.easeInOut.
How to Disable Animations When Reduce Motion is On
When “reduce motion” is on, instead of providing a less-intense version of the animation, you can remove it by setting the animation value as nil.
.onTapGesture {
withAnimation(reduceMotion ? nil : bouncyFeel){
isRotating.toggle()
isHidden.toggle()
}
}
Putting All Together
Combining all the above sections, you will have a sample code that looks like the one below.
/
// HamburgerToClose.swift
// Hamburger to Close
//
import SwiftUI
struct ReduceMotionSpring: View {
@State private var isRotating = false
@State private var isHidden = false
let subtleFeel = Animation.easeInOut // Use when Reduce Motion is On
let bouncyFeel = Animation.interpolatingSpring(stiffness: 300,
damping: 15)
// Detect and respond to reduce motion
@Environment(\.accessibilityReduceMotion) var reduceMotion
var body: some View {
VStack(spacing: 14){
Rectangle() // top
.frame(width: 64, height: 10)
.cornerRadius(4)
.rotationEffect(.degrees(isRotating ? 48 : 0), anchor:
.leading)
Rectangle() // middle
.frame(width: 64, height: 10)
.cornerRadius(4)
.scaleEffect(isHidden ? 0 : 1, anchor: isHidden ?
.trailing: .leading)
.opacity(isHidden ? 0 : 1)
Rectangle() // bottom
.frame(width: 64, height: 10)
.cornerRadius(4)
.rotationEffect(.degrees(isRotating ? -48 : 0), anchor:
.leading)
}
.accessibilityElement(children: .combine)
.accessibilityAddTraits(.isButton)
.accessibilityLabel("Hamburger menu")
.onTapGesture {
withAnimation(reduceMotion ? subtleFeel : bouncyFeel){
isRotating.toggle()
isHidden.toggle()
}
}
}
}
struct ReduceMotionSpring_Previews: PreviewProvider {
static var previews: some View {
ReduceMotionSpring()
.preferredColorScheme(.dark)
}
}
A preview of the code above.
Recap
In this article, you learned about the “reduce motion” feature on iOS. You discovered great examples of where “reduce motion” is used in real iOS apps. You now know how to respond to this accessibility setting in your iOS animations.
Where Do I Go From Here?
You can find the sample code used in this article or check out Purposeful iOS Animations on GitHub. You can create a new SwiftUI project in Xcode, and copy and paste the example code to test how it works in the simulator. For more SwiftUI animation tips and tricks, check the SwiftUI series of Stream Developers on YouTube channel. Enjoy!!!.
Software Engineer || Technical Product Manager || Creative!💡
2yGreat article. Very useful for increasing user accessibility options