Skip to content

Commit

Permalink
Play "reduced motion" marker if `UIAccessibility.isReduceMotionEnable…
Browse files Browse the repository at this point in the history
…d` is true (airbnb#2110)
  • Loading branch information
calda authored and iago849 committed Feb 8, 2024
1 parent 85e5133 commit fadcc63
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 60 deletions.
34 changes: 33 additions & 1 deletion Lottie.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
0887347B28F0CCDD00458627 /* LottieAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0887347428F0CCDD00458627 /* LottieAnimationView.swift */; };
0887347C28F0CCDD00458627 /* LottieAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0887347428F0CCDD00458627 /* LottieAnimationView.swift */; };
0887347D28F0CCDD00458627 /* LottieAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0887347428F0CCDD00458627 /* LottieAnimationView.swift */; };
08AB05552A61C20400DE86FD /* ReducedMotionOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AB05542A61C20400DE86FD /* ReducedMotionOption.swift */; };
08AB05562A61C20400DE86FD /* ReducedMotionOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AB05542A61C20400DE86FD /* ReducedMotionOption.swift */; };
08AB05572A61C20400DE86FD /* ReducedMotionOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AB05542A61C20400DE86FD /* ReducedMotionOption.swift */; };
08AB05592A61C5B700DE86FD /* DecodingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AB05582A61C5B700DE86FD /* DecodingStrategy.swift */; };
08AB055A2A61C5B700DE86FD /* DecodingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AB05582A61C5B700DE86FD /* DecodingStrategy.swift */; };
08AB055B2A61C5B700DE86FD /* DecodingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AB05582A61C5B700DE86FD /* DecodingStrategy.swift */; };
08AB055D2A61C5CC00DE86FD /* RenderingEngineOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AB055C2A61C5CC00DE86FD /* RenderingEngineOption.swift */; };
08AB055E2A61C5CC00DE86FD /* RenderingEngineOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AB055C2A61C5CC00DE86FD /* RenderingEngineOption.swift */; };
08AB055F2A61C5CC00DE86FD /* RenderingEngineOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AB055C2A61C5CC00DE86FD /* RenderingEngineOption.swift */; };
08C001F32A46150D00AB54BA /* Archive+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C001E02A46150D00AB54BA /* Archive+Helpers.swift */; };
08C001F42A46150D00AB54BA /* Archive+MemoryFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C001E12A46150D00AB54BA /* Archive+MemoryFile.swift */; };
08C001F52A46150D00AB54BA /* Archive+BackingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C001E22A46150D00AB54BA /* Archive+BackingConfiguration.swift */; };
Expand Down Expand Up @@ -828,6 +837,9 @@
0887347228F0CCDD00458627 /* LottieAnimationHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LottieAnimationHelpers.swift; sourceTree = "<group>"; };
0887347328F0CCDD00458627 /* LottieAnimationViewInitializers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LottieAnimationViewInitializers.swift; sourceTree = "<group>"; };
0887347428F0CCDD00458627 /* LottieAnimationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LottieAnimationView.swift; sourceTree = "<group>"; };
08AB05542A61C20400DE86FD /* ReducedMotionOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReducedMotionOption.swift; sourceTree = "<group>"; };
08AB05582A61C5B700DE86FD /* DecodingStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodingStrategy.swift; sourceTree = "<group>"; };
08AB055C2A61C5CC00DE86FD /* RenderingEngineOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingEngineOption.swift; sourceTree = "<group>"; };
08C001E02A46150D00AB54BA /* Archive+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Archive+Helpers.swift"; sourceTree = "<group>"; };
08C001E12A46150D00AB54BA /* Archive+MemoryFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Archive+MemoryFile.swift"; sourceTree = "<group>"; };
08C001E22A46150D00AB54BA /* Archive+BackingConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Archive+BackingConfiguration.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1148,6 +1160,17 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
08AB05532A61C1F000DE86FD /* Configuration */ = {
isa = PBXGroup;
children = (
2EAF59C727A0798700E00531 /* LottieConfiguration.swift */,
08AB05542A61C20400DE86FD /* ReducedMotionOption.swift */,
08AB05582A61C5B700DE86FD /* DecodingStrategy.swift */,
08AB055C2A61C5CC00DE86FD /* RenderingEngineOption.swift */,
);
path = Configuration;
sourceTree = "<group>";
};
08C001DE2A4614CF00AB54BA /* EmbeddedLibraries */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1807,8 +1830,8 @@
2EAF59C127A0798700E00531 /* Public */ = {
isa = PBXGroup;
children = (
08AB05532A61C1F000DE86FD /* Configuration */,
2EAF59C227A0798700E00531 /* macOS */,
2EAF59C727A0798700E00531 /* LottieConfiguration.swift */,
2EAF59C827A0798700E00531 /* Animation */,
6C4877E028FF20140005AF07 /* DotLottie */,
2EAF59CC27A0798700E00531 /* ImageProvider */,
Expand Down Expand Up @@ -2220,6 +2243,7 @@
08E207272A56014E002DCE17 /* SetContentProviding.swift in Sources */,
2E9C96DE2822F43100677516 /* GradientRenderLayer.swift in Sources */,
D453D8B428FF9EAA00D3F49C /* DefaultAnimationCache.swift in Sources */,
08AB055D2A61C5CC00DE86FD /* RenderingEngineOption.swift in Sources */,
2E9C966C2822F43100677516 /* LayerImageProvider.swift in Sources */,
2EAF5ABC27A0798700E00531 /* FilepathImageProvider.swift in Sources */,
2EAF5AE927A0798700E00531 /* AnimationTextProvider.swift in Sources */,
Expand Down Expand Up @@ -2252,6 +2276,7 @@
2E9C96F92822F43100677516 /* BaseCompositionLayer.swift in Sources */,
2EAF5A9B27A0798700E00531 /* BundleImageProvider.macOS.swift in Sources */,
2E9C969F2822F43100677516 /* TextAnimatorNode.swift in Sources */,
08AB05552A61C20400DE86FD /* ReducedMotionOption.swift in Sources */,
2EAF5AFB27A0798700E00531 /* SizeValueProvider.swift in Sources */,
2E9C97562822F43100677516 /* MathKit.swift in Sources */,
2E9C96902822F43100677516 /* EllipseNode.swift in Sources */,
Expand Down Expand Up @@ -2289,6 +2314,7 @@
2E9C96B72822F43100677516 /* NodePropertyMap.swift in Sources */,
2E9C97682822F43100677516 /* VectorsExtensions.swift in Sources */,
2E9C97232822F43100677516 /* RectangleAnimation.swift in Sources */,
08AB05592A61C5B700DE86FD /* DecodingStrategy.swift in Sources */,
2E450DAC283415D500E56D19 /* OpacityAnimation.swift in Sources */,
08E206E22A56014E002DCE17 /* ViewType.swift in Sources */,
2E9C96FC2822F43100677516 /* CALayer+setupLayerHierarchy.swift in Sources */,
Expand Down Expand Up @@ -2633,6 +2659,7 @@
2E9C970F2822F43100677516 /* CALayer+fillBounds.swift in Sources */,
08C002D12A46196300AB54BA /* Archive.swift in Sources */,
2E9C95FE2822F43100677516 /* SolidLayerModel.swift in Sources */,
08AB055E2A61C5CC00DE86FD /* RenderingEngineOption.swift in Sources */,
2E9C970C2822F43100677516 /* ValueProviderStore.swift in Sources */,
6C48780028FF20140005AF07 /* DotLottieAnimation.swift in Sources */,
2E9C97272822F43100677516 /* StrokeAnimation.swift in Sources */,
Expand Down Expand Up @@ -2702,6 +2729,7 @@
2E9C96AC2822F43100677516 /* GradientStrokeNode.swift in Sources */,
2EAF5AC027A0798700E00531 /* AnimatedSwitch.swift in Sources */,
2EAF5AC327A0798700E00531 /* BundleImageProvider.swift in Sources */,
08AB055A2A61C5B700DE86FD /* DecodingStrategy.swift in Sources */,
08E207492A56014E002DCE17 /* AnimatedProviding.swift in Sources */,
2E9C976C2822F43100677516 /* InterpolatableExtensions.swift in Sources */,
2E9C96EE2822F43100677516 /* ShapeItemLayer.swift in Sources */,
Expand All @@ -2710,6 +2738,7 @@
2E9C96312822F43100677516 /* TextAnimator.swift in Sources */,
2E9C96E82822F43100677516 /* ImageLayer.swift in Sources */,
2E9C972D2822F43100677516 /* StarAnimation.swift in Sources */,
08AB05562A61C20400DE86FD /* ReducedMotionOption.swift in Sources */,
2E9C96E22822F43100677516 /* LayerModel+makeAnimationLayer.swift in Sources */,
08E207372A56014E002DCE17 /* MakeViewProviding.swift in Sources */,
2E9C96A92822F43100677516 /* FillNode.swift in Sources */,
Expand Down Expand Up @@ -2779,6 +2808,7 @@
08E207292A56014E002DCE17 /* SetContentProviding.swift in Sources */,
2E9C96E02822F43100677516 /* GradientRenderLayer.swift in Sources */,
D453D8B328FF9EAA00D3F49C /* DefaultAnimationCache.swift in Sources */,
08AB055F2A61C5CC00DE86FD /* RenderingEngineOption.swift in Sources */,
2E9C966E2822F43100677516 /* LayerImageProvider.swift in Sources */,
2EAF5ABE27A0798700E00531 /* FilepathImageProvider.swift in Sources */,
2EAF5AEB27A0798700E00531 /* AnimationTextProvider.swift in Sources */,
Expand Down Expand Up @@ -2811,6 +2841,7 @@
2EAF5A9D27A0798700E00531 /* BundleImageProvider.macOS.swift in Sources */,
2E9C96A12822F43100677516 /* TextAnimatorNode.swift in Sources */,
2EAF5AFD27A0798700E00531 /* SizeValueProvider.swift in Sources */,
08AB05572A61C20400DE86FD /* ReducedMotionOption.swift in Sources */,
2E9C97582822F43100677516 /* MathKit.swift in Sources */,
2E9C96922822F43100677516 /* EllipseNode.swift in Sources */,
2E9C975B2822F43100677516 /* BezierPath.swift in Sources */,
Expand Down Expand Up @@ -2848,6 +2879,7 @@
2E9C97252822F43100677516 /* RectangleAnimation.swift in Sources */,
2E450DAE283415D500E56D19 /* OpacityAnimation.swift in Sources */,
2E9C96FE2822F43100677516 /* CALayer+setupLayerHierarchy.swift in Sources */,
08AB055B2A61C5B700DE86FD /* DecodingStrategy.swift in Sources */,
2E9C96A72822F43100677516 /* StrokeNode.swift in Sources */,
08E206E42A56014E002DCE17 /* ViewType.swift in Sources */,
2E9C95E72822F43100677516 /* ShapeTransform.swift in Sources */,
Expand Down
19 changes: 19 additions & 0 deletions Sources/Public/Animation/LottieAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,23 @@ public final class LottieAnimation: Codable, DictionaryInitializable {
/// Markers
let markers: [Marker]?
let markerMap: [String: Marker]?

/// The marker to use if "reduced motion" is enabled.
/// Supported marker names are case insensitive, and include:
/// - reduced motion
/// - reducedMotion
/// - reduced_motion
/// - reduced-motion
lazy private(set) var reducedMotionMarker: Marker? = {
let allowedReducedMotionMarkerNames = Set([
"reduced motion",
"reduced_motion",
"reduced-motion",
"reducedmotion",
])

return markers?.first(where: { marker in
allowedReducedMotionMarkerNames.contains(marker.name.lowercased())
})
}()
}
61 changes: 59 additions & 2 deletions Sources/Public/Animation/LottieAnimationLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ public class LottieAnimationLayer: CALayer {
///
/// - Parameter completion: An optional completion closure to be called when the animation completes playing.
open func play(completion: LottieCompletionBlock? = nil) {
guard let animation = animation else {
guard let animation = animation else { return }

if shouldOverrideWithReducedMotionAnimation {
playReducedMotionAnimation(completion: completion)
return
}

Expand All @@ -129,7 +132,10 @@ public class LottieAnimationLayer: CALayer {
loopMode: LottieLoopMode? = nil,
completion: LottieCompletionBlock? = nil)
{
guard let animation = animation else {
guard let animation = animation else { return }

if shouldOverrideWithReducedMotionAnimation {
playReducedMotionAnimation(completion: completion)
return
}

Expand Down Expand Up @@ -157,6 +163,11 @@ public class LottieAnimationLayer: CALayer {
loopMode: LottieLoopMode? = nil,
completion: LottieCompletionBlock? = nil)
{
if shouldOverrideWithReducedMotionAnimation {
playReducedMotionAnimation(completion: completion)
return
}

removeCurrentAnimationIfNecessary()
if let loopMode = loopMode {
/// Set the loop mode, if one was supplied
Expand Down Expand Up @@ -192,6 +203,11 @@ public class LottieAnimationLayer: CALayer {
loopMode: LottieLoopMode? = nil,
completion: LottieCompletionBlock? = nil)
{
if shouldOverrideWithReducedMotionAnimation {
playReducedMotionAnimation(completion: completion)
return
}

guard let animation = animation, let markers = animation.markerMap, let to = markers[toMarker] else {
return
}
Expand Down Expand Up @@ -232,6 +248,11 @@ public class LottieAnimationLayer: CALayer {
loopMode: LottieLoopMode? = nil,
completion: LottieCompletionBlock? = nil)
{
if shouldOverrideWithReducedMotionAnimation {
playReducedMotionAnimation(completion: completion)
return
}

guard let from = animation?.markerMap?[marker] else {
return
}
Expand Down Expand Up @@ -259,6 +280,11 @@ public class LottieAnimationLayer: CALayer {
///
/// - Parameter markers: The list of markers to play sequentially.
open func play(markers: [String]) {
if shouldOverrideWithReducedMotionAnimation {
playReducedMotionAnimation(completion: nil)
return
}

guard !markers.isEmpty else { return }

let markerToPlay = markers[0]
Expand Down Expand Up @@ -1241,6 +1267,22 @@ public class LottieAnimationLayer: CALayer {
/// The `LottieBackgroundBehavior` that was specified manually by setting `self.backgroundBehavior`
private var _backgroundBehavior: LottieBackgroundBehavior?

/// Whether or not the current animation should be overridden with
/// the marker matching the current "reduced motion" mode.
private var shouldOverrideWithReducedMotionAnimation: Bool {
reducedMotionMarker != nil
}

/// The marker that corresponds to the current "reduced motion" mode.
private var reducedMotionMarker: Marker? {
switch configuration.reducedMotionOption.currentReducedMotionMode {
case .standardMotion:
return nil
case .reducedMotion:
return animation?.reducedMotionMarker
}
}

private func loadAnimation(_ dotLottieAnimation: DotLottieFile.Animation) {
loopMode = dotLottieAnimation.configuration.loopMode
animationSpeed = CGFloat(dotLottieAnimation.configuration.speed)
Expand All @@ -1252,6 +1294,21 @@ public class LottieAnimationLayer: CALayer {
animation = dotLottieAnimation.animation
}

/// Plays the marker that corresponds to the current "reduced motion" mode if present.
private func playReducedMotionAnimation(completion: LottieCompletionBlock?) {
guard let reducedMotionMarker = reducedMotionMarker else { return }

// `play(marker:)` calls the `play(fromFrame:toFrame:)` method which calls this
// `playReducedMotionAnimation` method when `shouldOverrideWithReducedMotionAnimation`
// is `true`. To prevent infinite recursion, disable the reduced motion functionality
// until the end of this function.
let currentConfiguration = configuration
configuration.reducedMotionOption = .standardMotion
defer { configuration = currentConfiguration }

play(marker: reducedMotionMarker.name, completion: completion)
}

}

// MARK: - LottieLoopMode + caAnimationConfiguration
Expand Down
15 changes: 15 additions & 0 deletions Sources/Public/Configuration/DecodingStrategy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Created by Cal Stephens on 7/14/23.
// Copyright © 2023 Airbnb Inc. All rights reserved.

/// How animation files should be decoded
public enum DecodingStrategy: Hashable {
/// Use Codable. This is was the default strategy introduced on Lottie 3, but should be rarely
/// used as it's slower than `dictionaryBased`. Kept here for any possible compatibility issues
/// that may come up, but consider it soft-deprecated.
case legacyCodable

/// Manually deserialize a dictionary into an Animation.
/// This should be at least 2-3x faster than using Codable and due to that
/// it's the default as of Lottie 4.x.
case dictionaryBased
}
47 changes: 47 additions & 0 deletions Sources/Public/Configuration/LottieConfiguration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Created by Cal Stephens on 12/13/21.
// Copyright © 2021 Airbnb Inc. All rights reserved.

import QuartzCore

/// Global configuration options for Lottie animations
public struct LottieConfiguration: Hashable {

// MARK: Lifecycle

public init(
renderingEngine: RenderingEngineOption = .automatic,
decodingStrategy: DecodingStrategy = .dictionaryBased,
colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB(),
reducedMotionOption: ReducedMotionOption = .reducedMotion)
{
self.renderingEngine = renderingEngine
self.decodingStrategy = decodingStrategy
self.colorSpace = colorSpace
self.reducedMotionOption = reducedMotionOption
}

// MARK: Public

/// The global configuration of Lottie,
/// which applies to all `LottieAnimationView`s by default.
public static var shared = LottieConfiguration()

/// The rendering engine implementation to use when displaying an animation
/// - Defaults to `RenderingEngineOption.automatic`, which uses the
/// Core Animation rendering engine for supported animations, and
/// falls back to using the Main Thread rendering engine for
/// animations that use features not supported by the Core Animation engine.
public var renderingEngine: RenderingEngineOption

/// The decoding implementation to use when parsing an animation JSON file
public var decodingStrategy: DecodingStrategy

/// Options for controlling animation behavior in response to user / system "reduced motion" configuration.
/// - Defaults to `ReducedMotionOption.systemReducedMotionToggle`, which returns `.reducedMotion`
/// when the system `UIAccessibility.isReduceMotionEnabled` option is `true`.
public var reducedMotionOption: ReducedMotionOption

/// The color space to be used for rendering
/// - Defaults to `CGColorSpaceCreateDeviceRGB()`
public var colorSpace: CGColorSpace
}
Loading

0 comments on commit fadcc63

Please sign in to comment.