lottie-ios 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/.github/workflows/main.yml +117 -11
  2. package/Gemfile +1 -0
  3. package/Gemfile.lock +5 -0
  4. package/Lottie.xcodeproj/project.pbxproj +1542 -194
  5. package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  6. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (iOS).xcscheme +1 -1
  7. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (macOS).xcscheme +1 -1
  8. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (tvOS).xcscheme +4 -5
  9. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (visionOS).xcscheme +66 -0
  10. package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +18 -0
  11. package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +2 -11
  12. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  13. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +346 -102
  14. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +16 -5
  15. package/Package.resolved +2 -2
  16. package/Package.swift +16 -9
  17. package/README.md +2 -2
  18. package/Rakefile +122 -25
  19. package/Sources/Private/CoreAnimation/Animations/DropShadowAnimation.swift +160 -0
  20. package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +83 -10
  21. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +85 -79
  22. package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +7 -7
  23. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +67 -6
  24. package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +2 -2
  25. package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +9 -0
  26. package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +1 -0
  27. package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +1 -1
  28. package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +2 -0
  29. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +11 -13
  30. package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +20 -5
  31. package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +13 -3
  32. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +6 -4
  33. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift +263 -0
  34. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift +18 -0
  35. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift +16 -0
  36. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift +187 -0
  37. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift +32 -0
  38. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift +99 -0
  39. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift +8 -0
  40. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift +48 -0
  41. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift +158 -0
  42. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift +88 -0
  43. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift +54 -0
  44. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift +29 -0
  45. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift +39 -0
  46. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift +10 -0
  47. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift +57 -0
  48. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift +41 -0
  49. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift +41 -0
  50. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift +36 -0
  51. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift +49 -0
  52. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift +60 -0
  53. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift +38 -0
  54. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift +38 -0
  55. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift +37 -0
  56. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift +14 -0
  57. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift +34 -0
  58. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift +13 -0
  59. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift +41 -0
  60. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift +10 -0
  61. package/Sources/Private/EmbeddedLibraries/EpoxyCore/README.md +31 -0
  62. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift +46 -0
  63. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingView.swift +391 -0
  64. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift +44 -0
  65. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift +51 -0
  66. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift +172 -0
  67. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift +128 -0
  68. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +452 -0
  69. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift +148 -0
  70. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift +40 -0
  71. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift +43 -0
  72. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift +45 -0
  73. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift +36 -0
  74. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift +5 -0
  75. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift +42 -0
  76. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift +51 -0
  77. package/Sources/Private/EmbeddedLibraries/README.md +27 -0
  78. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+BackingConfiguration.swift +4 -4
  79. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+MemoryFile.swift +2 -2
  80. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Writing.swift +4 -4
  81. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive.swift +8 -7
  82. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Compression.swift +5 -5
  83. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/FileManager+ZIP.swift +4 -4
  84. package/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift +6 -0
  85. package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +3 -1
  86. package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +18 -5
  87. package/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift +31 -3
  88. package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +33 -8
  89. package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +8 -1
  90. package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift +13 -4
  91. package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +2 -2
  92. package/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift +1 -0
  93. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +4 -4
  94. package/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +1 -1
  95. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +3 -1
  96. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +2 -1
  97. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift +2 -1
  98. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
  99. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +2 -2
  100. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
  101. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +1 -1
  102. package/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +6 -2
  103. package/Sources/Private/Model/Assets/Asset.swift +8 -0
  104. package/Sources/Private/Model/Assets/AssetLibrary.swift +2 -2
  105. package/Sources/Private/Model/Assets/ImageAsset.swift +3 -3
  106. package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +16 -2
  107. package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +20 -7
  108. package/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +26 -0
  109. package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +8 -5
  110. package/Sources/Private/Model/LayerEffects/DropShadowEffect.swift +45 -0
  111. package/Sources/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift +38 -0
  112. package/Sources/Private/Model/LayerEffects/EffectValues/EffectValue.swift +98 -0
  113. package/Sources/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift +38 -0
  114. package/Sources/Private/Model/LayerEffects/LayerEffect.swift +103 -0
  115. package/Sources/Private/Model/LayerStyles/DropShadowStyle.swift +72 -0
  116. package/Sources/Private/Model/LayerStyles/LayerStyle.swift +85 -0
  117. package/Sources/Private/Model/Layers/LayerModel.swift +27 -0
  118. package/Sources/Private/Model/Objects/Marker.swift +1 -1
  119. package/Sources/Private/Model/Objects/Transform.swift +1 -2
  120. package/Sources/Private/Model/ShapeItems/GradientFill.swift +1 -1
  121. package/Sources/Private/Model/ShapeItems/GradientStroke.swift +2 -2
  122. package/Sources/Private/Model/ShapeItems/Merge.swift +1 -1
  123. package/Sources/Private/Model/ShapeItems/ShapeItem.swift +31 -26
  124. package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +0 -9
  125. package/Sources/Private/Model/ShapeItems/Star.swift +1 -1
  126. package/Sources/Private/Model/Text/Font.swift +2 -2
  127. package/Sources/Private/Model/Text/Glyph.swift +1 -1
  128. package/Sources/Private/RootAnimationLayer.swift +2 -2
  129. package/Sources/Private/Utility/Debugging/LayerDebugging.swift +3 -1
  130. package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +32 -2
  131. package/Sources/Private/Utility/Helpers/AnimationContext.swift +4 -2
  132. package/Sources/Private/Utility/Helpers/AnyEquatable.swift +24 -0
  133. package/Sources/Private/Utility/Helpers/Binding+Map.swift +18 -0
  134. package/Sources/Private/Utility/Helpers/View+ValueChanged.swift +20 -0
  135. package/Sources/Private/Utility/LottieAnimationSource.swift +41 -0
  136. package/Sources/Private/Utility/Primitives/BezierPath.swift +2 -2
  137. package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +2 -2
  138. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +1 -1
  139. package/Sources/Public/Animation/LottieAnimation.swift +21 -2
  140. package/Sources/Public/Animation/LottieAnimationLayer.swift +1464 -0
  141. package/Sources/Public/Animation/LottieAnimationView.swift +258 -735
  142. package/Sources/Public/Animation/LottiePlaybackMode.swift +117 -0
  143. package/Sources/Public/Animation/LottieView.swift +462 -0
  144. package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +2 -1
  145. package/Sources/Public/AnimationCache/DefaultAnimationCache.swift +5 -6
  146. package/Sources/Public/Configuration/DecodingStrategy.swift +15 -0
  147. package/Sources/Public/Configuration/LottieConfiguration.swift +47 -0
  148. package/Sources/Public/Configuration/ReducedMotionOption.swift +114 -0
  149. package/Sources/Public/{LottieConfiguration.swift → Configuration/RenderingEngineOption.swift} +2 -57
  150. package/Sources/Public/{iOS → Controls}/AnimatedButton.swift +56 -13
  151. package/Sources/Public/{iOS → Controls}/AnimatedControl.swift +80 -8
  152. package/Sources/Public/{iOS → Controls}/AnimatedSwitch.swift +71 -31
  153. package/Sources/Public/Controls/LottieButton.swift +122 -0
  154. package/Sources/Public/Controls/LottieSwitch.swift +144 -0
  155. package/Sources/Public/Controls/LottieViewType.swift +79 -0
  156. package/Sources/Public/DotLottie/DotLottieConfiguration.swift +24 -0
  157. package/Sources/Public/DotLottie/DotLottieFile.swift +21 -6
  158. package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +79 -0
  159. package/Sources/Public/DynamicProperties/AnimationKeypath.swift +11 -1
  160. package/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +14 -0
  161. package/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +13 -0
  162. package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +22 -5
  163. package/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +14 -0
  164. package/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +13 -0
  165. package/Sources/Public/FontProvider/AnimationFontProvider.swift +8 -0
  166. package/Sources/Public/ImageProvider/AnimationImageProvider.swift +24 -0
  167. package/Sources/Public/Keyframes/Keyframe.swift +6 -0
  168. package/Sources/Public/Primitives/Vectors.swift +2 -2
  169. package/Sources/Public/TextProvider/AnimationTextProvider.swift +79 -6
  170. package/Sources/Public/iOS/AnimationSubview.swift +1 -1
  171. package/Sources/Public/iOS/BundleImageProvider.swift +16 -2
  172. package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +2 -2
  173. package/Sources/Public/iOS/FilepathImageProvider.swift +22 -3
  174. package/Sources/Public/iOS/LottieAnimationViewBase.swift +7 -1
  175. package/Sources/Public/macOS/BundleImageProvider.macOS.swift +15 -1
  176. package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +21 -2
  177. package/lottie-ios.podspec +2 -1
  178. package/package.json +1 -1
  179. package/Sources/Private/Model/DotLottie/DotLottieConfiguration.swift +0 -15
  180. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Helpers.swift +0 -0
  181. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Progress.swift +0 -0
  182. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Reading.swift +0 -0
  183. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ReadingDeprecated.swift +0 -0
  184. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+WritingDeprecated.swift +0 -0
  185. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ZIP64.swift +0 -0
  186. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+CompressionDeprecated.swift +0 -0
  187. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Serialization.swift +0 -0
  188. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+Serialization.swift +0 -0
  189. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+ZIP64.swift +0 -0
  190. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry.swift +0 -0
  191. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/README.md +0 -0
  192. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/URL+ZIP.swift +0 -0
@@ -0,0 +1,1464 @@
1
+ //
2
+ // LottieAnimationLayer.swift
3
+ // Lottie
4
+ //
5
+
6
+ import Foundation
7
+ import QuartzCore
8
+
9
+ // MARK: - LottieAnimationLayer
10
+
11
+ /// A CALayer subclass for rendering Lottie animations.
12
+ /// - Also available as a SwiftUI view (`LottieView`) and a UIView subclass (`LottieAnimationView`)
13
+ public class LottieAnimationLayer: CALayer {
14
+
15
+ // MARK: Lifecycle
16
+
17
+ /// Initializes a LottieAnimationLayer with an animation.
18
+ public init(
19
+ animation: LottieAnimation?,
20
+ imageProvider: AnimationImageProvider? = nil,
21
+ textProvider: AnimationKeypathTextProvider = DefaultTextProvider(),
22
+ fontProvider: AnimationFontProvider = DefaultFontProvider(),
23
+ configuration: LottieConfiguration = .shared,
24
+ logger: LottieLogger = .shared)
25
+ {
26
+ self.animation = animation
27
+ self.imageProvider = imageProvider ?? BundleImageProvider(bundle: Bundle.main, searchPath: nil)
28
+ self.textProvider = textProvider
29
+ self.fontProvider = fontProvider
30
+ self.configuration = configuration
31
+ screenScale = 1
32
+ self.logger = logger
33
+ super.init()
34
+ makeAnimationLayer(usingEngine: configuration.renderingEngine)
35
+ if let animation = animation {
36
+ frame = animation.bounds
37
+ }
38
+ }
39
+
40
+ /// Initializes an LottieAnimationLayer with a .lottie file.
41
+ public init(
42
+ dotLottie: DotLottieFile?,
43
+ animationId: String? = nil,
44
+ textProvider: AnimationKeypathTextProvider = DefaultTextProvider(),
45
+ fontProvider: AnimationFontProvider = DefaultFontProvider(),
46
+ configuration: LottieConfiguration = .shared,
47
+ logger: LottieLogger = .shared)
48
+ {
49
+ let dotLottieAnimation = dotLottie?.animation(for: animationId)
50
+ animation = dotLottieAnimation?.animation
51
+ imageProvider = dotLottie?.imageProvider ?? BundleImageProvider(bundle: Bundle.main, searchPath: nil)
52
+ self.textProvider = textProvider
53
+ self.fontProvider = fontProvider
54
+ self.configuration = configuration
55
+ screenScale = 1
56
+ self.logger = logger
57
+ super.init()
58
+ loopMode = dotLottieAnimation?.configuration.loopMode ?? .playOnce
59
+ animationSpeed = CGFloat(dotLottieAnimation?.configuration.speed ?? 1)
60
+ makeAnimationLayer(usingEngine: configuration.renderingEngine)
61
+ if let animation = animation {
62
+ frame = animation.bounds
63
+ }
64
+ }
65
+
66
+ public init(
67
+ configuration: LottieConfiguration = .shared,
68
+ logger: LottieLogger = .shared)
69
+ {
70
+ animation = nil
71
+ imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil)
72
+ textProvider = DefaultTextProvider()
73
+ fontProvider = DefaultFontProvider()
74
+ self.configuration = configuration
75
+ screenScale = 1
76
+ self.logger = logger
77
+ super.init()
78
+ }
79
+
80
+ /// Called by CoreAnimation to create a shadow copy of this layer
81
+ /// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
82
+ override init(layer: Any) {
83
+ guard let typedLayer = layer as? Self else {
84
+ fatalError("\(Self.self).init(layer:) incorrectly called with \(type(of: layer))")
85
+ }
86
+
87
+ animation = typedLayer.animation
88
+ imageProvider = typedLayer.imageProvider
89
+ textProvider = typedLayer.textProvider
90
+ fontProvider = typedLayer.fontProvider
91
+ logger = typedLayer.logger
92
+ screenScale = typedLayer.screenScale
93
+ configuration = typedLayer.configuration
94
+ super.init(layer: typedLayer)
95
+ }
96
+
97
+ required init?(coder _: NSCoder) {
98
+ fatalError("init(coder:) has not been implemented")
99
+ }
100
+
101
+ // MARK: Open
102
+
103
+ /// Plays the animation from its current state to the end.
104
+ ///
105
+ /// - Parameter completion: An optional completion closure to be called when the animation completes playing.
106
+ open func play(completion: LottieCompletionBlock? = nil) {
107
+ guard let animation = animation else { return }
108
+
109
+ defer {
110
+ currentPlaybackMode = .toProgress(1, loopMode: loopMode)
111
+ }
112
+
113
+ if shouldOverrideWithReducedMotionAnimation {
114
+ playReducedMotionAnimation(completion: completion)
115
+ return
116
+ }
117
+
118
+ /// Build a context for the animation.
119
+ let context = AnimationContext(
120
+ playFrom: CGFloat(animation.startFrame),
121
+ playTo: CGFloat(animation.endFrame),
122
+ closure: completion)
123
+ removeCurrentAnimationIfNecessary()
124
+ addNewAnimationForContext(context)
125
+ }
126
+
127
+ /// Plays the animation from a progress (0-1) to a progress (0-1).
128
+ ///
129
+ /// - Parameter fromProgress: The start progress of the animation. If `nil` the animation will start at the current progress.
130
+ /// - Parameter toProgress: The end progress of the animation.
131
+ /// - Parameter loopMode: The loop behavior of the animation. If `nil` the layer's `loopMode` property will be used.
132
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
133
+ open func play(
134
+ fromProgress: AnimationProgressTime? = nil,
135
+ toProgress: AnimationProgressTime,
136
+ loopMode: LottieLoopMode? = nil,
137
+ completion: LottieCompletionBlock? = nil)
138
+ {
139
+ guard let animation = animation else { return }
140
+
141
+ defer {
142
+ currentPlaybackMode = .fromProgress(fromProgress, toProgress: toProgress, loopMode: loopMode ?? self.loopMode)
143
+ }
144
+
145
+ if shouldOverrideWithReducedMotionAnimation {
146
+ playReducedMotionAnimation(completion: completion)
147
+ return
148
+ }
149
+
150
+ removeCurrentAnimationIfNecessary()
151
+ if let loopMode = loopMode {
152
+ /// Set the loop mode, if one was supplied
153
+ self.loopMode = loopMode
154
+ }
155
+ let context = AnimationContext(
156
+ playFrom: animation.frameTime(forProgress: fromProgress ?? currentProgress),
157
+ playTo: animation.frameTime(forProgress: toProgress),
158
+ closure: completion)
159
+ addNewAnimationForContext(context)
160
+ }
161
+
162
+ /// Plays the animation from a start frame to an end frame in the animation's framerate.
163
+ ///
164
+ /// - Parameter fromFrame: The start frame of the animation. If `nil` the animation will start at the current frame.
165
+ /// - Parameter toFrame: The end frame of the animation.
166
+ /// - Parameter loopMode: The loop behavior of the animation. If `nil` the layer's `loopMode` property will be used.
167
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
168
+ open func play(
169
+ fromFrame: AnimationFrameTime? = nil,
170
+ toFrame: AnimationFrameTime,
171
+ loopMode: LottieLoopMode? = nil,
172
+ completion: LottieCompletionBlock? = nil)
173
+ {
174
+ defer {
175
+ currentPlaybackMode = .fromFrame(fromFrame, toFrame: toFrame, loopMode: loopMode ?? self.loopMode)
176
+ }
177
+
178
+ if shouldOverrideWithReducedMotionAnimation {
179
+ playReducedMotionAnimation(completion: completion)
180
+ return
181
+ }
182
+
183
+ removeCurrentAnimationIfNecessary()
184
+ if let loopMode = loopMode {
185
+ /// Set the loop mode, if one was supplied
186
+ self.loopMode = loopMode
187
+ }
188
+
189
+ let context = AnimationContext(
190
+ playFrom: fromFrame ?? currentFrame,
191
+ playTo: toFrame,
192
+ closure: completion)
193
+ addNewAnimationForContext(context)
194
+ }
195
+
196
+ /// Plays the animation from a named marker to another marker.
197
+ ///
198
+ /// Markers are point in time that are encoded into the Animation data and assigned
199
+ /// a name.
200
+ ///
201
+ /// NOTE: If markers are not found the play command will exit.
202
+ ///
203
+ /// - Parameter fromMarker: The start marker for the animation playback. If `nil` the
204
+ /// animation will start at the current progress.
205
+ /// - Parameter toMarker: The end marker for the animation playback.
206
+ /// - Parameter playEndMarkerFrame: A flag to determine whether or not to play the frame of the end marker. If the
207
+ /// end marker represents the end of the section to play, it should be to true. If the provided end marker
208
+ /// represents the beginning of the next section, it should be false.
209
+ /// - Parameter loopMode: The loop behavior of the animation. If `nil` the layer's `loopMode` property will be used.
210
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
211
+ open func play(
212
+ fromMarker: String? = nil,
213
+ toMarker: String,
214
+ playEndMarkerFrame: Bool = true,
215
+ loopMode: LottieLoopMode? = nil,
216
+ completion: LottieCompletionBlock? = nil)
217
+ {
218
+ defer {
219
+ currentPlaybackMode = .fromMarker(
220
+ fromMarker,
221
+ toMarker: toMarker,
222
+ playEndMarkerFrame: playEndMarkerFrame,
223
+ loopMode: loopMode ?? self.loopMode)
224
+ }
225
+
226
+ if shouldOverrideWithReducedMotionAnimation {
227
+ playReducedMotionAnimation(completion: completion)
228
+ return
229
+ }
230
+
231
+ guard let animation = animation, let markers = animation.markerMap, let to = markers[toMarker] else {
232
+ return
233
+ }
234
+
235
+ removeCurrentAnimationIfNecessary()
236
+ if let loopMode = loopMode {
237
+ /// Set the loop mode, if one was supplied
238
+ self.loopMode = loopMode
239
+ }
240
+
241
+ let fromTime: CGFloat
242
+ if let fromName = fromMarker, let from = markers[fromName] {
243
+ fromTime = CGFloat(from.frameTime)
244
+ } else {
245
+ fromTime = currentFrame
246
+ }
247
+
248
+ let playTo = playEndMarkerFrame ? CGFloat(to.frameTime) : CGFloat(to.frameTime) - 1
249
+ let context = AnimationContext(
250
+ playFrom: fromTime,
251
+ playTo: playTo,
252
+ closure: completion)
253
+ addNewAnimationForContext(context)
254
+ }
255
+
256
+ /// Plays the animation from a named marker to the end of the marker's duration.
257
+ ///
258
+ /// A marker is a point in time with an associated duration that is encoded into the
259
+ /// animation data and assigned a name.
260
+ ///
261
+ /// NOTE: If marker is not found the play command will exit.
262
+ ///
263
+ /// - Parameter marker: The start marker for the animation playback.
264
+ /// - Parameter loopMode: The loop behavior of the animation. If `nil` the layer's `loopMode` property will be used.
265
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
266
+ open func play(
267
+ marker: String,
268
+ loopMode: LottieLoopMode? = nil,
269
+ completion: LottieCompletionBlock? = nil)
270
+ {
271
+ guard let from = animation?.markerMap?[marker] else {
272
+ return
273
+ }
274
+
275
+ defer {
276
+ currentPlaybackMode = .marker(marker, loopMode: loopMode ?? self.loopMode)
277
+ }
278
+
279
+ if shouldOverrideWithReducedMotionAnimation {
280
+ playReducedMotionAnimation(completion: completion)
281
+ return
282
+ }
283
+
284
+ play(
285
+ fromFrame: from.frameTime,
286
+ toFrame: from.frameTime + from.durationFrameTime,
287
+ loopMode: loopMode,
288
+ completion: completion)
289
+ }
290
+
291
+ /// Plays the given markers sequentially in order.
292
+ ///
293
+ /// A marker is a point in time with an associated duration that is encoded into the
294
+ /// animation data and assigned a name. Multiple markers can be played sequentially
295
+ /// to create programmable animations.
296
+ ///
297
+ /// If a marker is not found, it will be skipped.
298
+ ///
299
+ /// If a marker doesn't have a duration value, it will play with a duration of 0
300
+ /// (effectively being skipped).
301
+ ///
302
+ /// If another animation is played (by calling any `play` method) while this
303
+ /// marker sequence is playing, the marker sequence will be cancelled.
304
+ ///
305
+ /// - Parameter markers: The list of markers to play sequentially.
306
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
307
+ open func play(
308
+ markers: [String],
309
+ completion: LottieCompletionBlock? = nil)
310
+ {
311
+ guard !markers.isEmpty else { return }
312
+
313
+ defer {
314
+ currentPlaybackMode = .markers(markers)
315
+ }
316
+
317
+ if shouldOverrideWithReducedMotionAnimation {
318
+ playReducedMotionAnimation(completion: nil)
319
+ return
320
+ }
321
+
322
+ let markerToPlay = markers[0]
323
+ let followingMarkers = Array(markers.dropFirst())
324
+
325
+ guard animation?.markerMap?[markerToPlay] != nil else {
326
+ play(markers: followingMarkers, completion: completion)
327
+ return
328
+ }
329
+
330
+ play(marker: markerToPlay, loopMode: .playOnce, completion: { [weak self] completed in
331
+ // If the completion handler is called with `completed: false` (which typically means
332
+ // that another animation was played by calling some `play` method),
333
+ // we should cancel the marker sequence and not play the next marker.
334
+ guard completed, let self = self else {
335
+ completion?(false)
336
+ return
337
+ }
338
+
339
+ if followingMarkers.isEmpty {
340
+ // If we don't have any more markers to play, then the marker sequence has completed.
341
+ completion?(completed)
342
+ } else {
343
+ self.play(markers: followingMarkers, completion: completion)
344
+ }
345
+ })
346
+ }
347
+
348
+ /// Stops the animation and resets the layer to its start frame.
349
+ ///
350
+ /// The completion closure will be called with `false`
351
+ open func stop() {
352
+ removeCurrentAnimation()
353
+ currentFrame = 0
354
+ }
355
+
356
+ /// Pauses the animation in its current state.
357
+ ///
358
+ /// The completion closure will be called with `false`
359
+ open func pause() {
360
+ removeCurrentAnimation()
361
+ currentPlaybackMode = .pause
362
+ }
363
+
364
+ /// Applies the given `LottiePlaybackMode` to this layer.
365
+ /// - Parameter animationCompletionHandler: A closure that is called after
366
+ /// an animation triggered by this method completes. This completion
367
+ /// handler is **not called** after setting the playback mode to a
368
+ /// mode that doesn't animate (e.g. `progress`, `frame`, `time`, or `pause`).
369
+ open func play(
370
+ _ playbackMode: LottiePlaybackMode,
371
+ animationCompletionHandler: LottieCompletionBlock? = nil)
372
+ {
373
+ switch playbackMode {
374
+ case .progress(let progress):
375
+ currentProgress = progress
376
+
377
+ case .frame(let frame):
378
+ currentFrame = frame
379
+
380
+ case .time(let time):
381
+ currentTime = time
382
+
383
+ case .pause:
384
+ pause()
385
+
386
+ case .fromProgress(let fromProgress, let toProgress, let loopMode):
387
+ play(
388
+ fromProgress: fromProgress,
389
+ toProgress: toProgress,
390
+ loopMode: loopMode,
391
+ completion: animationCompletionHandler)
392
+
393
+ case .fromFrame(let fromFrame, let toFrame, let loopMode):
394
+ play(
395
+ fromFrame: fromFrame,
396
+ toFrame: toFrame,
397
+ loopMode: loopMode,
398
+ completion: animationCompletionHandler)
399
+
400
+ case .fromMarker(let fromMarker, let toMarker, let playEndMarkerFrame, let loopMode):
401
+ play(
402
+ fromMarker: fromMarker,
403
+ toMarker: toMarker,
404
+ playEndMarkerFrame: playEndMarkerFrame,
405
+ loopMode: loopMode,
406
+ completion: animationCompletionHandler)
407
+
408
+ case .marker(let marker, let loopMode):
409
+ play(
410
+ marker: marker,
411
+ loopMode: loopMode,
412
+ completion: animationCompletionHandler)
413
+
414
+ case .markers(let markers):
415
+ play(markers: markers, completion: animationCompletionHandler)
416
+ }
417
+ }
418
+
419
+ // MARK: Public
420
+
421
+ /// The current `LottiePlaybackMode` that is being used
422
+ public private(set) var currentPlaybackMode: LottiePlaybackMode?
423
+
424
+ /// Value Providers that have been registered using `setValueProvider(_:keypath:)`
425
+ public private(set) var valueProviders = [AnimationKeypath: AnyValueProvider]()
426
+
427
+ /// A closure called when the animation layer has been loaded.
428
+ /// Will inform the receiver the type of rendering engine that is used for the layer.
429
+ public var animationLayerDidLoad:((_ animationLayer: LottieAnimationLayer, _ renderingEngine: RenderingEngineOption) -> Void)?
430
+
431
+ /// The configuration that this `LottieAnimationView` uses when playing its animation
432
+ public var configuration: LottieConfiguration {
433
+ didSet {
434
+ if configuration.renderingEngine != oldValue.renderingEngine {
435
+ makeAnimationLayer(usingEngine: configuration.renderingEngine)
436
+ }
437
+ }
438
+ }
439
+
440
+ /// The underlying CALayer created to display the content.
441
+ /// Use this property to change CALayer props like the content's transform, anchor point, etc.
442
+ public var animationLayer: CALayer? { rootAnimationLayer }
443
+
444
+ public var screenScale: CGFloat {
445
+ didSet {
446
+ rootAnimationLayer?.renderScale = screenScale
447
+ }
448
+ }
449
+
450
+ /// Describes the behavior of an AnimationView when the app is moved to the background.
451
+ ///
452
+ /// The default for the Main Thread animation engine is `pause`,
453
+ /// which pauses the animation when the application moves to
454
+ /// the background. This prevents the animation from consuming CPU
455
+ /// resources when not on-screen. The completion block is called with
456
+ /// `false` for completed.
457
+ ///
458
+ /// The default for the Core Animation engine is `continuePlaying`,
459
+ /// since the Core Animation engine does not have any CPU overhead.
460
+ public var backgroundBehavior: LottieBackgroundBehavior {
461
+ get {
462
+ let currentBackgroundBehavior = _backgroundBehavior ?? .default(for: currentRenderingEngine ?? .mainThread)
463
+
464
+ if
465
+ currentRenderingEngine == .mainThread,
466
+ _backgroundBehavior == .continuePlaying
467
+ {
468
+ logger.assertionFailure("""
469
+ `LottieBackgroundBehavior.continuePlaying` should not be used with the Main Thread
470
+ rendering engine, since this would waste CPU resources on playing an animation
471
+ that is not visible. Consider using a different background mode, or switching to
472
+ the Core Animation rendering engine (which does not have any CPU overhead).
473
+ """)
474
+ }
475
+
476
+ return currentBackgroundBehavior
477
+ }
478
+ set {
479
+ _backgroundBehavior = newValue
480
+ }
481
+ }
482
+
483
+ /// Sets the animation backing the animation layer. Setting this will clear the
484
+ /// layer's contents, completion blocks and current state. The new animation will
485
+ /// be loaded up and set to the beginning of its timeline.
486
+ public var animation: LottieAnimation? {
487
+ didSet {
488
+ makeAnimationLayer(usingEngine: configuration.renderingEngine)
489
+
490
+ if let animation = animation {
491
+ animationLoaded?(self, animation)
492
+ }
493
+ }
494
+ }
495
+
496
+ /// A closure that is called when `self.animation` is loaded. When setting this closure,
497
+ /// it is called immediately if `self.animation` is non-nil.
498
+ ///
499
+ /// When initializing a `LottieAnimationView`, the animation will either be loaded
500
+ /// synchronously (when loading a `LottieAnimation` from a .json file on disk)
501
+ /// or asynchronously (when loading a `DotLottieFile` from disk, or downloading
502
+ /// an animation from a URL). This closure is called in both cases once the
503
+ /// animation is loaded and applied, so can be a useful way to configure this
504
+ /// `LottieAnimationView` regardless of which initializer was used. For example:
505
+ ///
506
+ /// ```
507
+ /// let animationView: LottieAnimationView
508
+ ///
509
+ /// if loadDotLottieFile {
510
+ /// // Loads the .lottie file asynchronously
511
+ /// animationView = LottieAnimationView(dotLottieName: "animation")
512
+ /// } else {
513
+ /// // Loads the .json file synchronously
514
+ /// animationView = LottieAnimationView(name: "animation")
515
+ /// }
516
+ ///
517
+ /// animationView.animationLoaded = { animationView, animation in
518
+ /// // If using a .lottie file, this is called once the file finishes loading.
519
+ /// // If using a .json file, this is called immediately (since the animation is loaded synchronously).
520
+ /// animationView.play()
521
+ /// }
522
+ /// ```
523
+ public var animationLoaded: ((_ animationLayer: LottieAnimationLayer, _ animation: LottieAnimation) -> Void)? {
524
+ didSet {
525
+ if let animation = animation {
526
+ animationLoaded?(self, animation)
527
+ }
528
+ }
529
+ }
530
+
531
+ /// Sets the image provider for the animation layer. An image provider provides the
532
+ /// animation with its required image data.
533
+ ///
534
+ /// Setting this will cause the animation to reload its image contents.
535
+ public var imageProvider: AnimationImageProvider {
536
+ didSet {
537
+ rootAnimationLayer?.imageProvider = imageProvider.cachedImageProvider
538
+ reloadImages()
539
+ }
540
+ }
541
+
542
+ /// Sets the text provider for animation layer. A text provider provides the
543
+ /// animation with values for text layers
544
+ public var textProvider: AnimationKeypathTextProvider {
545
+ didSet {
546
+ rootAnimationLayer?.textProvider = textProvider
547
+ }
548
+ }
549
+
550
+ /// Sets the text provider for animation layer. A text provider provides the
551
+ /// animation with values for text layers
552
+ public var fontProvider: AnimationFontProvider {
553
+ didSet {
554
+ rootAnimationLayer?.fontProvider = fontProvider
555
+ }
556
+ }
557
+
558
+ /// Whether or not the animation is masked to the bounds. Defaults to true.
559
+ public var maskAnimationToBounds = true {
560
+ didSet {
561
+ animationLayer?.masksToBounds = maskAnimationToBounds
562
+ }
563
+ }
564
+
565
+ /// Returns `true` if the animation is currently playing.
566
+ public var isAnimationPlaying: Bool {
567
+ guard let animationLayer = rootAnimationLayer else {
568
+ return false
569
+ }
570
+
571
+ if let valueFromLayer = animationLayer.isAnimationPlaying {
572
+ return valueFromLayer
573
+ } else {
574
+ return animationLayer.animation(forKey: activeAnimationName) != nil
575
+ }
576
+ }
577
+
578
+ /// Sets the loop behavior for `play` calls. Defaults to `playOnce`
579
+ public var loopMode: LottieLoopMode = .playOnce {
580
+ didSet {
581
+ updateInFlightAnimation()
582
+ }
583
+ }
584
+
585
+ /// When `true` the animation layer will rasterize its contents when not animating.
586
+ /// Rasterizing will improve performance of static animations.
587
+ ///
588
+ /// Note: this will not produce crisp results at resolutions above the animations natural resolution.
589
+ ///
590
+ /// Defaults to `false`
591
+ public var shouldRasterizeWhenIdle = false {
592
+ didSet {
593
+ updateRasterizationState()
594
+ }
595
+ }
596
+
597
+ /// Sets the current animation time with a Progress Time
598
+ ///
599
+ /// Note: Setting this will stop the current animation, if any.
600
+ /// Note 2: If `animation` is nil, setting this will fallback to 0
601
+ public var currentProgress: AnimationProgressTime {
602
+ set {
603
+ if let animation = animation {
604
+ currentFrame = animation.frameTime(forProgress: newValue)
605
+ currentPlaybackMode = .progress(newValue)
606
+ } else {
607
+ currentFrame = 0
608
+ }
609
+ }
610
+ get {
611
+ if let animation = animation {
612
+ return animation.progressTime(forFrame: currentFrame)
613
+ } else {
614
+ return 0
615
+ }
616
+ }
617
+ }
618
+
619
+ /// Sets the current animation time with a time in seconds.
620
+ ///
621
+ /// Note: Setting this will stop the current animation, if any.
622
+ /// Note 2: If `animation` is nil, setting this will fallback to 0
623
+ public var currentTime: TimeInterval {
624
+ set {
625
+ if let animation = animation {
626
+ currentFrame = animation.frameTime(forTime: newValue)
627
+ currentPlaybackMode = .time(newValue)
628
+ } else {
629
+ currentFrame = 0
630
+ }
631
+ }
632
+ get {
633
+ if let animation = animation {
634
+ return animation.time(forFrame: currentFrame)
635
+ } else {
636
+ return 0
637
+ }
638
+ }
639
+ }
640
+
641
+ /// Sets the current animation time with a frame in the animations framerate.
642
+ ///
643
+ /// Note: Setting this will stop the current animation, if any.
644
+ public var currentFrame: AnimationFrameTime {
645
+ set {
646
+ removeCurrentAnimationIfNecessary()
647
+ updateAnimationFrame(newValue)
648
+ currentPlaybackMode = .frame(currentFrame)
649
+ }
650
+ get {
651
+ rootAnimationLayer?.currentFrame ?? 0
652
+ }
653
+ }
654
+
655
+ /// Returns the current animation frame while an animation is playing.
656
+ public var realtimeAnimationFrame: AnimationFrameTime {
657
+ isAnimationPlaying ? rootAnimationLayer?.presentation()?.currentFrame ?? currentFrame : currentFrame
658
+ }
659
+
660
+ /// Returns the current animation frame while an animation is playing.
661
+ public var realtimeAnimationProgress: AnimationProgressTime {
662
+ if let animation = animation {
663
+ return animation.progressTime(forFrame: realtimeAnimationFrame)
664
+ }
665
+ return 0
666
+ }
667
+
668
+ /// Sets the speed of the animation playback. Defaults to 1
669
+ public var animationSpeed: CGFloat = 1 {
670
+ didSet {
671
+ updateInFlightAnimation()
672
+ }
673
+ }
674
+
675
+ /// When `true` the animation will play back at the framerate encoded in the
676
+ /// `LottieAnimation` model. When `false` the animation will play at the framerate
677
+ /// of the device.
678
+ ///
679
+ /// Defaults to false
680
+ public var respectAnimationFrameRate = false {
681
+ didSet {
682
+ rootAnimationLayer?.respectAnimationFrameRate = respectAnimationFrameRate
683
+ }
684
+ }
685
+
686
+ /// The rendering engine currently being used by this layer.
687
+ /// - This will only be `nil` in cases where the configuration is `automatic`
688
+ /// but a `RootAnimationLayer` hasn't been constructed yet
689
+ public var currentRenderingEngine: RenderingEngine? {
690
+ switch configuration.renderingEngine {
691
+ case .specific(let engine):
692
+ return engine
693
+
694
+ case .automatic:
695
+ guard let animationLayer = animationLayer else {
696
+ return nil
697
+ }
698
+
699
+ if animationLayer is CoreAnimationLayer {
700
+ return .coreAnimation
701
+ } else {
702
+ return .mainThread
703
+ }
704
+ }
705
+ }
706
+
707
+ /// Whether or not the Main Thread rendering engine should use `forceDisplayUpdate()`
708
+ /// when rendering each individual frame.
709
+ /// - The main thread rendering engine implements optimizations to decrease the amount
710
+ /// of properties that have to be re-rendered on each frame. There are some cases
711
+ /// where this can result in bugs / incorrect behavior, so we allow it to be disabled.
712
+ /// - Forcing a full render on every frame will decrease performance, and is not recommended
713
+ /// except as a workaround to a bug in the main thread rendering engine.
714
+ /// - Has no effect when using the Core Animation rendering engine.
715
+ public var mainThreadRenderingEngineShouldForceDisplayUpdateOnEachFrame = false {
716
+ didSet {
717
+ (rootAnimationLayer as? MainThreadAnimationLayer)?.forceDisplayUpdateOnEachFrame
718
+ = mainThreadRenderingEngineShouldForceDisplayUpdateOnEachFrame
719
+ }
720
+ }
721
+
722
+ /// Sets the lottie file backing the animation layer. Setting this will clear the
723
+ /// layer's contents, completion blocks and current state. The new animation will
724
+ /// be loaded up and set to the beginning of its timeline.
725
+ /// The loopMode, animationSpeed and imageProvider will be set according
726
+ /// to lottie file settings
727
+ /// - Parameters:
728
+ /// - animationId: Internal animation id to play. Optional
729
+ /// Defaults to play first animation in file.
730
+ /// - dotLottieFile: Lottie file to play
731
+ public func loadAnimation(
732
+ _ animationId: String? = nil,
733
+ from dotLottieFile: DotLottieFile)
734
+ {
735
+ guard let dotLottieAnimation = dotLottieFile.animation(for: animationId) else { return }
736
+ loadAnimation(dotLottieAnimation)
737
+ }
738
+
739
+ /// Sets the lottie file backing the animation layer. Setting this will clear the
740
+ /// layer's contents, completion blocks and current state. The new animation will
741
+ /// be loaded up and set to the beginning of its timeline.
742
+ /// The loopMode, animationSpeed and imageProvider will be set according
743
+ /// to lottie file settings
744
+ /// - Parameters:
745
+ /// - atIndex: Internal animation index to play.
746
+ /// Defaults to play first animation in file.
747
+ /// - dotLottieFile: Lottie file to play
748
+ public func loadAnimation(
749
+ atIndex index: Int,
750
+ from dotLottieFile: DotLottieFile)
751
+ {
752
+ guard let dotLottieAnimation = dotLottieFile.animation(at: index) else { return }
753
+ loadAnimation(dotLottieAnimation)
754
+ }
755
+
756
+ /// Reloads the images supplied to the animation from the `imageProvider`
757
+ public func reloadImages() {
758
+ rootAnimationLayer?.reloadImages()
759
+ }
760
+
761
+ /// Forces the LottieAnimationView to redraw its contents.
762
+ public func forceDisplayUpdate() {
763
+ rootAnimationLayer?.forceDisplayUpdate()
764
+ }
765
+
766
+ /// Sets a ValueProvider for the specified keypath. The value provider will be set
767
+ /// on all properties that match the keypath.
768
+ ///
769
+ /// Nearly all properties of a Lottie animation can be changed at runtime using a
770
+ /// combination of `Animation Keypaths` and `Value Providers`.
771
+ /// Setting a ValueProvider on a keypath will cause the animation to update its
772
+ /// contents and read the new Value Provider.
773
+ ///
774
+ /// A value provider provides a typed value on a frame by frame basis.
775
+ ///
776
+ /// - Parameter valueProvider: The new value provider for the properties.
777
+ /// - Parameter keypath: The keypath used to search for properties.
778
+ ///
779
+ /// Example:
780
+ /// ```
781
+ /// /// A keypath that finds the color value for all `Fill 1` nodes.
782
+ /// let fillKeypath = AnimationKeypath(keypath: "**.Fill 1.Color")
783
+ /// /// A Color Value provider that returns a reddish color.
784
+ /// let redValueProvider = ColorValueProvider(Color(r: 1, g: 0.2, b: 0.3, a: 1))
785
+ /// /// Set the provider on the animationView.
786
+ /// animationView.setValueProvider(redValueProvider, keypath: fillKeypath)
787
+ /// ```
788
+ public func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) {
789
+ guard let animationLayer = rootAnimationLayer else { return }
790
+
791
+ valueProviders[keypath] = valueProvider
792
+ animationLayer.setValueProvider(valueProvider, keypath: keypath)
793
+ }
794
+
795
+ /// Reads the value of a property specified by the Keypath.
796
+ /// Returns nil if no property is found.
797
+ ///
798
+ /// - Parameter for: The keypath used to search for the property.
799
+ /// - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used.
800
+ public func getValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? {
801
+ rootAnimationLayer?.getValue(for: keypath, atFrame: atFrame)
802
+ }
803
+
804
+ /// Reads the original value of a property specified by the Keypath.
805
+ /// This will ignore any value providers and can be useful when implementing a value providers that makes change to the original value from the animation.
806
+ /// Returns nil if no property is found.
807
+ ///
808
+ /// - Parameter for: The keypath used to search for the property.
809
+ /// - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used.
810
+ public func getOriginalValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? {
811
+ rootAnimationLayer?.getOriginalValue(for: keypath, atFrame: atFrame)
812
+ }
813
+
814
+ /// Logs all child keypaths.
815
+ public func logHierarchyKeypaths() {
816
+ rootAnimationLayer?.logHierarchyKeypaths()
817
+ }
818
+
819
+ /// Computes and returns a list of all child keypaths in the current animation.
820
+ /// The returned list is the same as the log output of `logHierarchyKeypaths()`
821
+ public func allHierarchyKeypaths() -> [String] {
822
+ rootAnimationLayer?.allHierarchyKeypaths() ?? []
823
+ }
824
+
825
+ /// Converts a CGRect from the LottieAnimationView's coordinate space into the
826
+ /// coordinate space of the layer found at Keypath.
827
+ ///
828
+ /// If no layer is found, nil is returned
829
+ ///
830
+ /// - Parameter rect: The CGRect to convert.
831
+ /// - Parameter toLayerAt: The keypath used to find the layer.
832
+ public func convert(_ rect: CGRect, toLayerAt keypath: AnimationKeypath?) -> CGRect? {
833
+ guard let animationLayer = rootAnimationLayer else { return nil }
834
+ guard let keypath = keypath else {
835
+ return convert(rect, to: animationLayer)
836
+ }
837
+ guard let sublayer = animationLayer.layer(for: keypath) else {
838
+ return nil
839
+ }
840
+ setNeedsLayout()
841
+ layoutIfNeeded()
842
+ forceDisplayUpdate()
843
+ return animationLayer.convert(rect, to: sublayer)
844
+ }
845
+
846
+ /// Converts a CGPoint from the LottieAnimationView's coordinate space into the
847
+ /// coordinate space of the layer found at Keypath.
848
+ ///
849
+ /// If no layer is found, nil is returned
850
+ ///
851
+ /// - Parameter point: The CGPoint to convert.
852
+ /// - Parameter toLayerAt: The keypath used to find the layer.
853
+ public func convert(_ point: CGPoint, toLayerAt keypath: AnimationKeypath?) -> CGPoint? {
854
+ guard let animationLayer = rootAnimationLayer else { return nil }
855
+ guard let keypath = keypath else {
856
+ return convert(point, to: animationLayer)
857
+ }
858
+ guard let sublayer = animationLayer.layer(for: keypath) else {
859
+ return nil
860
+ }
861
+ setNeedsLayout()
862
+ layoutIfNeeded()
863
+ forceDisplayUpdate()
864
+ return animationLayer.convert(point, to: sublayer)
865
+ }
866
+
867
+ /// Sets the enabled state of all animator nodes found with the keypath search.
868
+ /// This can be used to interactively enable / disable parts of the animation.
869
+ ///
870
+ /// - Parameter isEnabled: When true the animator nodes affect the rendering tree. When false the node is removed from the tree.
871
+ /// - Parameter keypath: The keypath used to find the node(s).
872
+ public func setNodeIsEnabled(isEnabled: Bool, keypath: AnimationKeypath) {
873
+ guard let animationLayer = rootAnimationLayer else { return }
874
+ let nodes = animationLayer.animatorNodes(for: keypath)
875
+ if let nodes = nodes {
876
+ for node in nodes {
877
+ node.isEnabled = isEnabled
878
+ }
879
+ forceDisplayUpdate()
880
+ }
881
+ }
882
+
883
+ /// Markers are a way to describe a point in time by a key name.
884
+ ///
885
+ /// Markers are encoded into animation JSON. By using markers a designer can mark
886
+ /// playback points for a developer to use without having to worry about keeping
887
+ /// track of animation frames. If the animation file is updated, the developer
888
+ /// does not need to update playback code.
889
+ ///
890
+ /// Returns the Progress Time for the marker named. Returns nil if no marker found.
891
+ public func progressTime(forMarker named: String) -> AnimationProgressTime? {
892
+ guard let animation = animation else {
893
+ return nil
894
+ }
895
+ return animation.progressTime(forMarker: named)
896
+ }
897
+
898
+ /// Markers are a way to describe a point in time by a key name.
899
+ ///
900
+ /// Markers are encoded into animation JSON. By using markers a designer can mark
901
+ /// playback points for a developer to use without having to worry about keeping
902
+ /// track of animation frames. If the animation file is updated, the developer
903
+ /// does not need to update playback code.
904
+ ///
905
+ /// Returns the Frame Time for the marker named. Returns nil if no marker found.
906
+ public func frameTime(forMarker named: String) -> AnimationFrameTime? {
907
+ guard let animation = animation else {
908
+ return nil
909
+ }
910
+ return animation.frameTime(forMarker: named)
911
+ }
912
+
913
+ /// Markers are a way to describe a point in time and a duration by a key name.
914
+ ///
915
+ /// Markers are encoded into animation JSON. By using markers a designer can mark
916
+ /// playback points for a developer to use without having to worry about keeping
917
+ /// track of animation frames. If the animation file is updated, the developer
918
+ /// does not need to update playback code.
919
+ ///
920
+ /// - Returns: The duration frame time for the marker, or `nil` if no marker found.
921
+ public func durationFrameTime(forMarker named: String) -> AnimationFrameTime? {
922
+ guard let animation = animation else {
923
+ return nil
924
+ }
925
+ return animation.durationFrameTime(forMarker: named)
926
+ }
927
+
928
+ public func updateAnimationForBackgroundState() {
929
+ if let currentContext = animationContext {
930
+ switch backgroundBehavior {
931
+ case .stop:
932
+ removeCurrentAnimation()
933
+ updateAnimationFrame(currentContext.playFrom)
934
+ case .pause:
935
+ removeCurrentAnimation()
936
+ case .pauseAndRestore:
937
+ currentContext.closure.ignoreDelegate = true
938
+ removeCurrentAnimation()
939
+ /// Keep the stale context around for when the app enters the foreground.
940
+ animationContext = currentContext
941
+ case .forceFinish:
942
+ removeCurrentAnimation()
943
+ updateAnimationFrame(currentContext.playTo)
944
+ case .continuePlaying:
945
+ break
946
+ }
947
+ }
948
+ }
949
+
950
+ public func updateAnimationForForegroundState(wasWaitingToPlayAnimation: Bool) {
951
+ if let currentContext = animationContext {
952
+ if wasWaitingToPlayAnimation {
953
+ addNewAnimationForContext(currentContext)
954
+ } else if backgroundBehavior == .pauseAndRestore {
955
+ /// Restore animation from saved state
956
+ updateInFlightAnimation()
957
+ }
958
+ }
959
+ }
960
+
961
+ // MARK: Internal
962
+
963
+ var rootAnimationLayer: RootAnimationLayer? = nil
964
+
965
+ /// Context describing the animation that is currently playing in this `LottieAnimationView`
966
+ /// - When non-nil, an animation is currently playing in this layer. Otherwise,
967
+ /// the layer is paused on a specific frame.
968
+ fileprivate(set) var animationContext: AnimationContext?
969
+
970
+ var hasAnimationContext: Bool {
971
+ animationContext != nil
972
+ }
973
+
974
+ /// Set animation name from Interface Builder
975
+ var animationName: String? {
976
+ didSet {
977
+ animation = animationName.flatMap {
978
+ LottieAnimation.named($0, animationCache: nil)
979
+ }
980
+ }
981
+ }
982
+
983
+ func updateRasterizationState() {
984
+ if isAnimationPlaying {
985
+ animationLayer?.shouldRasterize = false
986
+ } else {
987
+ animationLayer?.shouldRasterize = shouldRasterizeWhenIdle
988
+ }
989
+ }
990
+
991
+ /// Updates the animation frame. Does not affect any current animations
992
+ func updateAnimationFrame(_ newFrame: CGFloat) {
993
+ // In performance tests, we have to wrap the animation layer setup
994
+ // in a `CATransaction` in order for the layers to be deallocated at
995
+ // the correct time. The `CATransaction`s in this method interfere
996
+ // with the ones managed by the performance test, and aren't actually
997
+ // necessary in a headless environment, so we disable them.
998
+ if TestHelpers.performanceTestsAreRunning {
999
+ rootAnimationLayer?.currentFrame = newFrame
1000
+ rootAnimationLayer?.forceDisplayUpdate()
1001
+ return
1002
+ }
1003
+
1004
+ CATransaction.begin()
1005
+ CATransaction.setCompletionBlock {
1006
+ self.rootAnimationLayer?.forceDisplayUpdate()
1007
+ }
1008
+ CATransaction.setDisableActions(true)
1009
+ rootAnimationLayer?.currentFrame = newFrame
1010
+ CATransaction.commit()
1011
+ }
1012
+
1013
+ /// Updates an in flight animation.
1014
+ func updateInFlightAnimation() {
1015
+ guard let animationContext = animationContext else { return }
1016
+
1017
+ guard animationContext.closure.animationState != .complete else {
1018
+ // Tried to re-add an already completed animation. Cancel.
1019
+ self.animationContext = nil
1020
+ return
1021
+ }
1022
+
1023
+ /// Tell existing context to ignore its closure
1024
+ animationContext.closure.ignoreDelegate = true
1025
+
1026
+ /// Make a new context, stealing the completion block from the previous.
1027
+ let newContext = AnimationContext(
1028
+ playFrom: animationContext.playFrom,
1029
+ playTo: animationContext.playTo,
1030
+ closure: animationContext.closure.completionBlock)
1031
+
1032
+ /// Remove current animation, and freeze the current frame.
1033
+ let pauseFrame = realtimeAnimationFrame
1034
+ rootAnimationLayer?.removeAnimation(forKey: activeAnimationName)
1035
+ rootAnimationLayer?.currentFrame = pauseFrame
1036
+
1037
+ addNewAnimationForContext(newContext)
1038
+ }
1039
+
1040
+ func loadAnimation(_ animationSource: LottieAnimationSource?) {
1041
+ switch animationSource {
1042
+ case .lottieAnimation(let animation):
1043
+ self.animation = animation
1044
+ case .dotLottieFile(let dotLottieFile):
1045
+ loadAnimation(from: dotLottieFile)
1046
+ case nil:
1047
+ animation = nil
1048
+ }
1049
+ }
1050
+
1051
+ // MARK: Fileprivate
1052
+
1053
+ fileprivate var _activeAnimationName: String = LottieAnimationLayer.animationName
1054
+ fileprivate var animationID = 0
1055
+
1056
+ fileprivate var activeAnimationName: String {
1057
+ switch rootAnimationLayer?.primaryAnimationKey {
1058
+ case .specific(let animationKey):
1059
+ return animationKey
1060
+ case .managed, nil:
1061
+ return _activeAnimationName
1062
+ }
1063
+ }
1064
+
1065
+ /// Stops the current in flight animation and freezes the animation in its current state.
1066
+ fileprivate func removeCurrentAnimation() {
1067
+ guard animationContext != nil else { return }
1068
+ let pauseFrame = realtimeAnimationFrame
1069
+ animationLayer?.removeAnimation(forKey: activeAnimationName)
1070
+ updateAnimationFrame(pauseFrame)
1071
+ animationContext = nil
1072
+ }
1073
+
1074
+ fileprivate func makeAnimationLayer(usingEngine renderingEngine: RenderingEngineOption) {
1075
+ /// Remove current animation if any
1076
+ removeCurrentAnimation()
1077
+
1078
+ if let oldAnimation = animationLayer {
1079
+ oldAnimation.removeFromSuperlayer()
1080
+ }
1081
+
1082
+ guard let animation = animation else {
1083
+ return
1084
+ }
1085
+ let rootAnimationLayer: RootAnimationLayer?
1086
+ switch renderingEngine {
1087
+ case .automatic:
1088
+ rootAnimationLayer = makeAutomaticEngineLayer(for: animation)
1089
+ case .specific(.coreAnimation):
1090
+ rootAnimationLayer = makeCoreAnimationLayer(for: animation)
1091
+ case .specific(.mainThread):
1092
+ rootAnimationLayer = makeMainThreadAnimationLayer(for: animation)
1093
+ }
1094
+
1095
+ guard let animationLayer = rootAnimationLayer else {
1096
+ return
1097
+ }
1098
+
1099
+ animationLayer.lottieAnimationLayer = self
1100
+
1101
+ for (keypath, valueProvider) in valueProviders {
1102
+ animationLayer.setValueProvider(valueProvider, keypath: keypath)
1103
+ }
1104
+
1105
+ animationLayerDidLoad?(self, renderingEngine)
1106
+
1107
+ animationLayer.renderScale = screenScale
1108
+
1109
+ addSublayer(animationLayer)
1110
+ self.rootAnimationLayer = animationLayer
1111
+ reloadImages()
1112
+ animationLayer.setNeedsDisplay()
1113
+ setNeedsLayout()
1114
+ currentFrame = CGFloat(animation.startFrame)
1115
+ }
1116
+
1117
+ fileprivate func makeMainThreadAnimationLayer(for animation: LottieAnimation) -> MainThreadAnimationLayer {
1118
+ let mainThreadAnimationLayer = MainThreadAnimationLayer(
1119
+ animation: animation,
1120
+ imageProvider: imageProvider.cachedImageProvider,
1121
+ textProvider: textProvider,
1122
+ fontProvider: fontProvider,
1123
+ maskAnimationToBounds: maskAnimationToBounds,
1124
+ logger: logger)
1125
+
1126
+ mainThreadAnimationLayer.forceDisplayUpdateOnEachFrame = mainThreadRenderingEngineShouldForceDisplayUpdateOnEachFrame
1127
+ return mainThreadAnimationLayer
1128
+ }
1129
+
1130
+ fileprivate func makeCoreAnimationLayer(for animation: LottieAnimation) -> CoreAnimationLayer? {
1131
+ do {
1132
+ let coreAnimationLayer = try CoreAnimationLayer(
1133
+ animation: animation,
1134
+ imageProvider: imageProvider.cachedImageProvider,
1135
+ textProvider: textProvider,
1136
+ fontProvider: fontProvider,
1137
+ maskAnimationToBounds: maskAnimationToBounds,
1138
+ compatibilityTrackerMode: .track,
1139
+ logger: logger)
1140
+
1141
+ coreAnimationLayer.didSetUpAnimation = { [logger] compatibilityIssues in
1142
+ logger.assert(
1143
+ compatibilityIssues.isEmpty,
1144
+ "Encountered Core Animation compatibility issues while setting up animation:\n"
1145
+ + compatibilityIssues.map { $0.description }.joined(separator: "\n") + "\n\n"
1146
+ + """
1147
+ This animation cannot be rendered correctly by the Core Animation engine.
1148
+ To resolve this issue, you can use `RenderingEngineOption.automatic`, which automatically falls back
1149
+ to the Main Thread rendering engine when necessary, or just use `RenderingEngineOption.mainThread`.
1150
+
1151
+ """)
1152
+ }
1153
+
1154
+ return coreAnimationLayer
1155
+ } catch {
1156
+ // This should never happen, because we initialize the `CoreAnimationLayer` with
1157
+ // `CompatibilityTracker.Mode.track` (which reports errors in `didSetUpAnimation`,
1158
+ // not by throwing).
1159
+ logger.assertionFailure("Encountered unexpected error \(error)")
1160
+ return nil
1161
+ }
1162
+ }
1163
+
1164
+ fileprivate func makeAutomaticEngineLayer(for animation: LottieAnimation) -> CoreAnimationLayer? {
1165
+ do {
1166
+ // Attempt to set up the Core Animation layer. This can either throw immediately in `init`,
1167
+ // or throw an error later in `CALayer.display()` that will be reported in `didSetUpAnimation`.
1168
+ let coreAnimationLayer = try CoreAnimationLayer(
1169
+ animation: animation,
1170
+ imageProvider: imageProvider.cachedImageProvider,
1171
+ textProvider: textProvider,
1172
+ fontProvider: fontProvider,
1173
+ maskAnimationToBounds: maskAnimationToBounds,
1174
+ compatibilityTrackerMode: .abort,
1175
+ logger: logger)
1176
+
1177
+ coreAnimationLayer.didSetUpAnimation = { [weak self] issues in
1178
+ self?.automaticEngineLayerDidSetUpAnimation(issues)
1179
+ }
1180
+
1181
+ return coreAnimationLayer
1182
+ } catch {
1183
+ if case CompatibilityTracker.Error.encounteredCompatibilityIssue(let compatibilityIssue) = error {
1184
+ automaticEngineLayerDidSetUpAnimation([compatibilityIssue])
1185
+ } else {
1186
+ // This should never happen, because we expect `CoreAnimationLayer` to only throw
1187
+ // `CompatibilityTracker.Error.encounteredCompatibilityIssue` errors.
1188
+ logger.assertionFailure("Encountered unexpected error \(error)")
1189
+ automaticEngineLayerDidSetUpAnimation([])
1190
+ }
1191
+
1192
+ return nil
1193
+ }
1194
+ }
1195
+
1196
+ // Handles any compatibility issues with the Core Animation engine
1197
+ // by falling back to the Main Thread engine
1198
+ fileprivate func automaticEngineLayerDidSetUpAnimation(_ compatibilityIssues: [CompatibilityIssue]) {
1199
+ // If there weren't any compatibility issues, then there's nothing else to do
1200
+ if compatibilityIssues.isEmpty {
1201
+ return
1202
+ }
1203
+
1204
+ logger.warn(
1205
+ "Encountered Core Animation compatibility issue while setting up animation:\n"
1206
+ + compatibilityIssues.map { $0.description }.joined(separator: "\n") + "\n"
1207
+ + """
1208
+ This animation may have additional compatibility issues, but animation setup was cancelled early to avoid wasted work.
1209
+
1210
+ Automatically falling back to Main Thread rendering engine. This fallback comes with some additional performance
1211
+ overhead, which can be reduced by manually specifying that this animation should always use the Main Thread engine.
1212
+
1213
+ """)
1214
+
1215
+ let animationContext = animationContext
1216
+ let currentFrame = currentFrame
1217
+
1218
+ // Disable the completion handler delegate before tearing down the `CoreAnimationLayer`
1219
+ // and building the `MainThreadAnimationLayer`. Otherwise deinitializing the
1220
+ // `CoreAnimationLayer` would trigger the animation completion handler even though
1221
+ // the animation hasn't even started playing yet.
1222
+ animationContext?.closure.ignoreDelegate = true
1223
+
1224
+ makeAnimationLayer(usingEngine: .mainThread)
1225
+
1226
+ // Set up the Main Thread animation layer using the same configuration that
1227
+ // was being used by the previous Core Animation layer
1228
+ self.currentFrame = currentFrame
1229
+
1230
+ if let animationContext = animationContext {
1231
+ // `AnimationContext.closure` (`AnimationCompletionDelegate`) is a reference type
1232
+ // that is the animation layer's `CAAnimationDelegate`, and holds a reference to
1233
+ // the animation layer. Reusing a single instance across different animation layers
1234
+ // can cause the animation setup to fail, so we create a copy of the `animationContext`:
1235
+ addNewAnimationForContext(AnimationContext(
1236
+ playFrom: animationContext.playFrom,
1237
+ playTo: animationContext.playTo,
1238
+ closure: animationContext.closure.completionBlock))
1239
+ }
1240
+ }
1241
+
1242
+ /// Removes the current animation and pauses the animation at the current frame
1243
+ /// if necessary before setting up a new animation.
1244
+ /// - This is not necessary with the Core Animation engine, and skipping
1245
+ /// this step lets us avoid building the animations twice (once paused
1246
+ /// and once again playing)
1247
+ /// - This method should only be called immediately before setting up another
1248
+ /// animation -- otherwise this LottieAnimationView could be put in an inconsistent state.
1249
+ fileprivate func removeCurrentAnimationIfNecessary() {
1250
+ switch currentRenderingEngine {
1251
+ case .mainThread:
1252
+ removeCurrentAnimation()
1253
+ case .coreAnimation, nil:
1254
+ // We still need to remove the `animationContext`, since it should only be present
1255
+ // when an animation is actually playing. Without this calling `removeCurrentAnimationIfNecessary()`
1256
+ // and then setting the animation to a specific paused frame would put this
1257
+ // `LottieAnimationView` in an inconsistent state.
1258
+ animationContext = nil
1259
+ }
1260
+ }
1261
+
1262
+ /// Adds animation to animation layer and sets the delegate. If animation layer or animation are nil, exits.
1263
+ fileprivate func addNewAnimationForContext(_ animationContext: AnimationContext) {
1264
+ guard let animationlayer = rootAnimationLayer, let animation = animation else {
1265
+ return
1266
+ }
1267
+
1268
+ self.animationContext = animationContext
1269
+
1270
+ switch currentRenderingEngine {
1271
+ case .mainThread:
1272
+ // On the CALayer side, just play all animations immediately. The UIView side
1273
+ // will handle queueing animations.
1274
+ break
1275
+
1276
+ case .coreAnimation, nil:
1277
+ // The Core Animation engine automatically batches animation setup to happen
1278
+ // in `CALayer.display()`, which won't be called until the layer is on-screen,
1279
+ // so we don't need to defer animation setup at this layer.
1280
+ break
1281
+ }
1282
+
1283
+ animationID = animationID + 1
1284
+ _activeAnimationName = LottieAnimationLayer.animationName + String(animationID)
1285
+
1286
+ if let coreAnimationLayer = animationlayer as? CoreAnimationLayer {
1287
+ var animationContext = animationContext
1288
+
1289
+ // Core Animation doesn't natively support negative speed values,
1290
+ // so instead we can swap `playFrom` / `playTo`
1291
+ if animationSpeed < 0 {
1292
+ let temp = animationContext.playFrom
1293
+ animationContext.playFrom = animationContext.playTo
1294
+ animationContext.playTo = temp
1295
+ }
1296
+
1297
+ var timingConfiguration = CoreAnimationLayer.CAMediaTimingConfiguration(
1298
+ autoreverses: loopMode.caAnimationConfiguration.autoreverses,
1299
+ repeatCount: loopMode.caAnimationConfiguration.repeatCount,
1300
+ speed: abs(Float(animationSpeed)))
1301
+
1302
+ // The animation should start playing from the `currentFrame`,
1303
+ // if `currentFrame` is included in the time range being played.
1304
+ let lowerBoundTime = min(animationContext.playFrom, animationContext.playTo)
1305
+ let upperBoundTime = max(animationContext.playFrom, animationContext.playTo)
1306
+ if (lowerBoundTime ..< upperBoundTime).contains(round(currentFrame)) {
1307
+ // We have to configure this differently depending on the loop mode:
1308
+ switch loopMode {
1309
+ // When playing exactly once (and not looping), we can just set the
1310
+ // `playFrom` time to be the `currentFrame`. Since the animation duration
1311
+ // is based on `playFrom` and `playTo`, this automatically truncates the
1312
+ // duration (so the animation stops playing at `playFrom`).
1313
+ // - Don't do this if the animation is already at that frame
1314
+ // (e.g. playing from 100% to 0% when the animation is already at 0%)
1315
+ // since that would cause the animation to not play at all.
1316
+ case .playOnce:
1317
+ if animationContext.playTo != currentFrame {
1318
+ animationContext.playFrom = currentFrame
1319
+ }
1320
+
1321
+ // When looping, we specifically _don't_ want to affect the duration of the animation,
1322
+ // since that would affect the duration of all subsequent loops. We just want to adjust
1323
+ // the duration of the _first_ loop. Instead of setting `playFrom`, we just add a `timeOffset`
1324
+ // so the first loop begins at `currentTime` but all subsequent loops are the standard duration.
1325
+ default:
1326
+ if animationSpeed < 0 {
1327
+ timingConfiguration.timeOffset = animation.time(forFrame: animationContext.playFrom) - currentTime
1328
+ } else {
1329
+ timingConfiguration.timeOffset = currentTime - animation.time(forFrame: animationContext.playFrom)
1330
+ }
1331
+ }
1332
+ }
1333
+
1334
+ // If attempting to play a zero-duration animation, just pause on that single frame instead
1335
+ if animationContext.playFrom == animationContext.playTo {
1336
+ currentFrame = animationContext.playTo
1337
+ animationContext.closure.completionBlock?(true)
1338
+ return
1339
+ }
1340
+
1341
+ coreAnimationLayer.playAnimation(configuration: .init(
1342
+ animationContext: animationContext,
1343
+ timingConfiguration: timingConfiguration))
1344
+
1345
+ return
1346
+ }
1347
+
1348
+ /// At this point there is no animation on animationLayer and its state is set.
1349
+
1350
+ let framerate = animation.framerate
1351
+
1352
+ let playFrom = animationContext.playFrom.clamp(animation.startFrame, animation.endFrame)
1353
+ let playTo = animationContext.playTo.clamp(animation.startFrame, animation.endFrame)
1354
+
1355
+ let duration = ((max(playFrom, playTo) - min(playFrom, playTo)) / CGFloat(framerate))
1356
+
1357
+ let playingForward: Bool =
1358
+ (
1359
+ (animationSpeed > 0 && playFrom < playTo) ||
1360
+ (animationSpeed < 0 && playTo < playFrom))
1361
+
1362
+ var startFrame = currentFrame.clamp(min(playFrom, playTo), max(playFrom, playTo))
1363
+ if startFrame == playTo {
1364
+ startFrame = playFrom
1365
+ }
1366
+
1367
+ let timeOffset: TimeInterval = playingForward
1368
+ ? Double(startFrame - min(playFrom, playTo)) / framerate
1369
+ : Double(max(playFrom, playTo) - startFrame) / framerate
1370
+
1371
+ let layerAnimation = CABasicAnimation(keyPath: "currentFrame")
1372
+ layerAnimation.fromValue = playFrom
1373
+ layerAnimation.toValue = playTo
1374
+ layerAnimation.speed = Float(animationSpeed)
1375
+ layerAnimation.duration = TimeInterval(duration)
1376
+ layerAnimation.fillMode = CAMediaTimingFillMode.both
1377
+ layerAnimation.repeatCount = loopMode.caAnimationConfiguration.repeatCount
1378
+ layerAnimation.autoreverses = loopMode.caAnimationConfiguration.autoreverses
1379
+
1380
+ layerAnimation.isRemovedOnCompletion = false
1381
+ if timeOffset != 0 {
1382
+ let currentLayerTime = convertTime(CACurrentMediaTime(), from: nil)
1383
+ layerAnimation.beginTime = currentLayerTime - (timeOffset * 1 / Double(abs(animationSpeed)))
1384
+ }
1385
+ layerAnimation.delegate = animationContext.closure
1386
+ animationContext.closure.animationLayer = animationlayer
1387
+ animationContext.closure.animationKey = activeAnimationName
1388
+
1389
+ animationlayer.add(layerAnimation, forKey: activeAnimationName)
1390
+ updateRasterizationState()
1391
+ }
1392
+
1393
+ // MARK: Private
1394
+
1395
+ static private let animationName = "Lottie"
1396
+
1397
+ private let logger: LottieLogger
1398
+
1399
+ /// The `LottieBackgroundBehavior` that was specified manually by setting `self.backgroundBehavior`
1400
+ private var _backgroundBehavior: LottieBackgroundBehavior?
1401
+
1402
+ /// Whether or not the current animation should be overridden with
1403
+ /// the marker matching the current "reduced motion" mode.
1404
+ private var shouldOverrideWithReducedMotionAnimation: Bool {
1405
+ reducedMotionMarker != nil
1406
+ }
1407
+
1408
+ /// The marker that corresponds to the current "reduced motion" mode.
1409
+ private var reducedMotionMarker: Marker? {
1410
+ switch configuration.reducedMotionOption.currentReducedMotionMode {
1411
+ case .standardMotion:
1412
+ return nil
1413
+ case .reducedMotion:
1414
+ return animation?.reducedMotionMarker
1415
+ }
1416
+ }
1417
+
1418
+ private func loadAnimation(_ dotLottieAnimation: DotLottieFile.Animation) {
1419
+ loopMode = dotLottieAnimation.configuration.loopMode
1420
+ animationSpeed = CGFloat(dotLottieAnimation.configuration.speed)
1421
+
1422
+ if let imageProvider = dotLottieAnimation.configuration.imageProvider {
1423
+ self.imageProvider = imageProvider
1424
+ }
1425
+
1426
+ animation = dotLottieAnimation.animation
1427
+ }
1428
+
1429
+ /// Plays the marker that corresponds to the current "reduced motion" mode if present.
1430
+ private func playReducedMotionAnimation(completion: LottieCompletionBlock?) {
1431
+ guard let reducedMotionMarker = reducedMotionMarker else { return }
1432
+
1433
+ // `play(marker:)` calls the `play(fromFrame:toFrame:)` method which calls this
1434
+ // `playReducedMotionAnimation` method when `shouldOverrideWithReducedMotionAnimation`
1435
+ // is `true`. To prevent infinite recursion, disable the reduced motion functionality
1436
+ // until the end of this function.
1437
+ let currentConfiguration = configuration
1438
+ configuration.reducedMotionOption = .standardMotion
1439
+ defer { configuration = currentConfiguration }
1440
+
1441
+ play(marker: reducedMotionMarker.name, completion: completion)
1442
+ }
1443
+
1444
+ }
1445
+
1446
+ // MARK: - LottieLoopMode + caAnimationConfiguration
1447
+
1448
+ extension LottieLoopMode {
1449
+ /// The `CAAnimation` configuration that reflects this mode
1450
+ var caAnimationConfiguration: (repeatCount: Float, autoreverses: Bool) {
1451
+ switch self {
1452
+ case .playOnce:
1453
+ return (repeatCount: 1, autoreverses: false)
1454
+ case .loop:
1455
+ return (repeatCount: .greatestFiniteMagnitude, autoreverses: false)
1456
+ case .autoReverse:
1457
+ return (repeatCount: .greatestFiniteMagnitude, autoreverses: true)
1458
+ case .repeat(let amount):
1459
+ return (repeatCount: amount, autoreverses: false)
1460
+ case .repeatBackwards(let amount):
1461
+ return (repeatCount: amount, autoreverses: true)
1462
+ }
1463
+ }
1464
+ }