Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Play "reduced motion" marker if UIAccessibility.isReduceMotionEnabled is true #2110

Merged
merged 3 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this to its own file

/// 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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this to its own file and added the reducedMotionOption property


// 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
Loading