Skip to Content
Course content

206: Reduce Motion and Accessibility Settings

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I’ve seen this happen in countless code reviews: a developer spends hours polishing a "wow" moment—maybe a card that slides in from the side with a spring bounce or a complex zooming transition—only to realize they've created a nightmare for users with vestibular disorders. When a user enables "Reduce Motion" in their iOS settings, they aren't just asking for a faster app; they're telling you that certain types of movement make them physically ill.

Here is a snippet of code I recently encountered. It’s a simple onboarding screen where a "Welcome" card slides up from the bottom of the screen when the view appears.

struct OnboardingView: View {
    @State private var isVisible = false

    var body: some View {
        VStack {
            if isVisible {
                Text("Welcome to the App!")
                    .font(.largeTitle)
                    .transition(.move(edge: .bottom))
            }
        }
        .onAppear {
            withAnimation(.spring()) {
                isVisible = true
            }
        }
    }
}

The Vertigo Trigger: Ignoring System Motion Preferences

At first glance, this looks great. It's fluid and modern. But there's a major problem: it's hard-coded to animate. If a user has Reduce Motion turned on in their System Settings, this view will still slide up from the bottom. For someone prone to motion sickness, that sliding movement can cause genuine discomfort. We're essentially ignoring a direct request from the user about how they need to interact with their device.

The mistake here is assuming that a "good" animation is always good. In professional Swift development, your animations should be conditional. You need to ask the system, "Does the user want to see this motion?" before you trigger it.

Listening to the Environment with accessibilityReduceMotion

To fix this, we use the @Environment property wrapper. SwiftUI provides a specific key called \.accessibilityReduceMotion. This is a Boolean that tells you exactly what the user has selected in their system settings. If it's true, we should either strip the animation entirely or replace the "sliding" motion with something more subtle, like a simple cross-fade.

Here is how I would rewrite that onboarding view to be actually accessible:

struct OnboardingView: View {
    @State private var isVisible = false
    // We pull the user's preference from the environment
    @Environment(\.accessibilityReduceMotion) var reduceMotion

    var body: some View {
        VStack {
            if isVisible {
                Text("Welcome to the App!")
                    .font(.largeTitle)
                    // Use a ternary to decide the transition
                    .transition(reduceMotion ? .opacity : .move(edge: .bottom))
            }
        }
        .onAppear {
            // If reduceMotion is true, we can use a much simpler animation 
            // or remove the spring bounce entirely.
            withAnimation(reduceMotion ? .linear(duration: 0.2) : .spring()) {
                isVisible = true
            }
        }
    }
}

Notice what happened here. I didn't just delete the animation—because some users who "Reduce Motion" still appreciate a gentle fade-in so the UI doesn't just "pop" into existence. Instead, I swapped a spatial transition (moving from point A to B) for a non-spatial transition (changing opacity). This preserves the polish of the app without triggering the physiological response that Reduce Motion is designed to prevent.

I recommend making this a habit. Whenever you reach for .transition(.move(...)) or .offset() inside an animation block, immediately ask yourself if that movement is necessary or if it should be gated behind the reduceMotion check.




📋 Practical Task

Adapting the Dashboard Slide-In for Motion Sensitivity

You have been handed a Dashboard view that features a "Notification Banner" that slides in from the top of the screen whenever a new alert arrives. Currently, it uses a high-energy spring animation and a .move(edge: .top) transition, which violates the project's accessibility guidelines.

Your Task: Modify the provided code to respect the user's system settings. If accessibilityReduceMotion is enabled, the banner should simply fade in (using .opacity) with a short, linear animation instead of sliding in with a spring.

struct NotificationBanner: View {
    @State private var isShowing = false
    // TODO: Add the environment variable here

    var body: some View {
        VStack {
            if isShowing {
                Text("You have a new message!")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
                    // TODO: Update this transition based on accessibility settings
                    .transition(.move(edge: .top))
            }
            
            Button("Show Notification") {
                // TODO: Update this animation based on accessibility settings
                withAnimation(.spring()) {
                    isShowing = true
                }
            }
        }
    }
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.