lottie-ios 3.2.3 → 3.4.2

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 (434) hide show
  1. package/.github/actions/setup/action.yml +32 -0
  2. package/.github/issue_template.md +6 -23
  3. package/.github/workflows/main.yml +98 -0
  4. package/.github/workflows/stale_issues.yml +17 -0
  5. package/.spi.yml +6 -0
  6. package/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +7 -0
  7. package/.swiftpm/xcode/package.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  8. package/.swiftpm/xcode/package.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  9. package/.swiftpm/xcode/xcuserdata/{brandonwithrow.xcuserdatad → cal.xcuserdatad}/xcschemes/xcschememanagement.plist +25 -15
  10. package/.swiftpm/xcode/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  11. package/Gemfile +4 -0
  12. package/Gemfile.lock +102 -0
  13. package/Lottie.xcodeproj/project.pbxproj +2011 -1972
  14. package/Lottie.xcodeproj/project.xcworkspace/contents.xcworkspacedata +1 -1
  15. package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  16. package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_iOS.xcscheme → Lottie (iOS).xcscheme } +15 -18
  17. package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_tvOS.xcscheme → Lottie (macOS).xcscheme } +5 -18
  18. package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_macOS.xcscheme → Lottie (tvOS).xcscheme } +5 -18
  19. package/Lottie.xcodeproj/xcuserdata/cal.xcuserdatad/xcschemes/xcschememanagement.plist +37 -0
  20. package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +24 -0
  21. package/Lottie.xcworkspace/contents.xcworkspacedata +10 -0
  22. package/Lottie.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  23. package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +34 -0
  24. package/Lottie.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  25. package/{Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad → Lottie.xcworkspace/xcuserdata/cal.xcuserdatad}/xcdebugger/Breakpoints_v2.xcbkptlist +2 -2
  26. package/Lottie.xcworkspace/xcuserdata/cal.xcuserdatad/xcdebugger/Expressions.xcexplist +153 -0
  27. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  28. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +22 -0
  29. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +138 -0
  30. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +30 -0
  31. package/Package.swift +5 -15
  32. package/README.md +40 -60
  33. package/Rakefile +160 -0
  34. package/Sources/Private/CoreAnimation/Animations/CAAnimation+TimingConfiguration.swift +81 -0
  35. package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +447 -0
  36. package/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift +81 -0
  37. package/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift +41 -0
  38. package/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift +55 -0
  39. package/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift +210 -0
  40. package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +229 -0
  41. package/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift +52 -0
  42. package/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift +58 -0
  43. package/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift +257 -0
  44. package/Sources/Private/CoreAnimation/Animations/StarAnimation.swift +124 -0
  45. package/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift +73 -0
  46. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +205 -0
  47. package/Sources/Private/CoreAnimation/Animations/VisibilityAnimation.swift +37 -0
  48. package/Sources/Private/CoreAnimation/CompatibilityTracker.swift +130 -0
  49. package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +497 -0
  50. package/Sources/Private/CoreAnimation/Extensions/CALayer+fillBounds.swift +35 -0
  51. package/Sources/Private/CoreAnimation/Extensions/KeyframeGroup+exactlyOneKeyframe.swift +37 -0
  52. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combinedIfPossible.swift +154 -0
  53. package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +83 -0
  54. package/Sources/Private/CoreAnimation/Layers/BaseAnimationLayer.swift +33 -0
  55. package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +88 -0
  56. package/Sources/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift +167 -0
  57. package/Sources/Private/CoreAnimation/Layers/GradientRenderLayer.swift +94 -0
  58. package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +79 -0
  59. package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +64 -0
  60. package/Sources/Private/CoreAnimation/Layers/MaskCompositionLayer.swift +138 -0
  61. package/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift +140 -0
  62. package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +85 -0
  63. package/Sources/Private/CoreAnimation/Layers/ShapeItemLayer.swift +315 -0
  64. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +390 -0
  65. package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +47 -0
  66. package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +91 -0
  67. package/Sources/Private/CoreAnimation/Layers/TransformLayer.swift +11 -0
  68. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +139 -0
  69. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/CompositionLayer.swift +94 -86
  70. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/ImageCompositionLayer.swift +24 -21
  71. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/MaskContainerLayer.swift +75 -54
  72. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/NullCompositionLayer.swift +5 -5
  73. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/PreCompositionLayer.swift +59 -43
  74. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/ShapeCompositionLayer.swift +22 -20
  75. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/SolidCompositionLayer.swift +26 -17
  76. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/TextCompositionLayer.swift +42 -36
  77. package/{lottie-swift/src/Private/LayerContainers/AnimationContainer.swift → Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift} +200 -140
  78. package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +47 -0
  79. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/CompositionLayersInitializer.swift +33 -24
  80. package/{lottie-swift/src/Private/LayerContainers/Utility/TextLayer.swift → Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift} +149 -106
  81. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/InvertedMatteLayer.swift +26 -24
  82. package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +41 -0
  83. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/LayerImageProvider.swift +20 -16
  84. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +40 -0
  85. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/LayerTransformNode.swift +82 -67
  86. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Extensions/ItemsExtension.swift +9 -4
  87. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/NodeProperty.swift +29 -21
  88. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift +16 -10
  89. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift +6 -6
  90. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift +5 -5
  91. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +10 -8
  92. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueContainer.swift +25 -21
  93. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift +23 -17
  94. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift +22 -17
  95. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +110 -79
  96. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift +22 -16
  97. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift +19 -15
  98. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift +22 -20
  99. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift +22 -23
  100. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +247 -0
  101. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift +30 -24
  102. package/{lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift → Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift} +93 -66
  103. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift +23 -19
  104. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +139 -0
  105. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +170 -0
  106. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/RectNode.swift +95 -58
  107. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift +42 -29
  108. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/StarNode.swift +108 -69
  109. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +155 -0
  110. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift +51 -37
  111. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +57 -42
  112. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +66 -58
  113. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +169 -0
  114. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +138 -119
  115. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/AnimatorNode.swift +80 -79
  116. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/PathNode.swift +5 -3
  117. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/RenderNode.swift +22 -17
  118. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +27 -24
  119. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift +34 -25
  120. package/Sources/Private/Model/Animation.swift +160 -0
  121. package/Sources/Private/Model/Assets/Asset.swift +43 -0
  122. package/{lottie-swift/src → Sources}/Private/Model/Assets/AssetLibrary.swift +40 -13
  123. package/Sources/Private/Model/Assets/ImageAsset.swift +112 -0
  124. package/{lottie-swift/src → Sources}/Private/Model/Assets/PrecompAsset.swift +20 -10
  125. package/Sources/Private/Model/DictionaryInitializable.swift +67 -0
  126. package/Sources/Private/Model/Extensions/Bundle.swift +34 -0
  127. package/{lottie-swift/src → Sources}/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +8 -4
  128. package/Sources/Private/Model/Keyframes/KeyframeData.swift +113 -0
  129. package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +222 -0
  130. package/{lottie-swift/src → Sources}/Private/Model/Layers/ImageLayerModel.swift +21 -11
  131. package/Sources/Private/Model/Layers/LayerModel.swift +228 -0
  132. package/{lottie-swift/src → Sources}/Private/Model/Layers/PreCompLayerModel.swift +39 -22
  133. package/{lottie-swift/src → Sources}/Private/Model/Layers/ShapeLayerModel.swift +23 -12
  134. package/{lottie-swift/src → Sources}/Private/Model/Layers/SolidLayerModel.swift +31 -19
  135. package/{lottie-swift/src → Sources}/Private/Model/Layers/TextLayerModel.swift +33 -19
  136. package/Sources/Private/Model/Objects/DashPattern.swift +44 -0
  137. package/Sources/Private/Model/Objects/Marker.swift +33 -0
  138. package/Sources/Private/Model/Objects/Mask.swift +80 -0
  139. package/Sources/Private/Model/Objects/Transform.swift +179 -0
  140. package/Sources/Private/Model/ShapeItems/Ellipse.swift +74 -0
  141. package/Sources/Private/Model/ShapeItems/Fill.swift +74 -0
  142. package/Sources/Private/Model/ShapeItems/GradientFill.swift +137 -0
  143. package/Sources/Private/Model/ShapeItems/GradientStroke.swift +185 -0
  144. package/Sources/Private/Model/ShapeItems/Group.swift +48 -0
  145. package/{lottie-swift/src → Sources}/Private/Model/ShapeItems/Merge.swift +29 -12
  146. package/Sources/Private/Model/ShapeItems/Rectangle.swift +72 -0
  147. package/Sources/Private/Model/ShapeItems/Repeater.swift +135 -0
  148. package/Sources/Private/Model/ShapeItems/Shape.swift +56 -0
  149. package/Sources/Private/Model/ShapeItems/ShapeItem.swift +163 -0
  150. package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +135 -0
  151. package/Sources/Private/Model/ShapeItems/Star.swift +131 -0
  152. package/Sources/Private/Model/ShapeItems/Stroke.swift +101 -0
  153. package/Sources/Private/Model/ShapeItems/Trim.swift +77 -0
  154. package/Sources/Private/Model/Text/Font.swift +61 -0
  155. package/{lottie-swift/src → Sources}/Private/Model/Text/Glyph.swift +63 -39
  156. package/Sources/Private/Model/Text/TextAnimator.swift +164 -0
  157. package/Sources/Private/Model/Text/TextDocument.swift +123 -0
  158. package/Sources/Private/RootAnimationLayer.swift +51 -0
  159. package/{lottie-swift/src → Sources}/Private/Utility/Debugging/AnimatorNodeDebugging.swift +7 -7
  160. package/{lottie-swift/src → Sources}/Private/Utility/Debugging/LayerDebugging.swift +72 -48
  161. package/Sources/Private/Utility/Debugging/TestHelpers.swift +10 -0
  162. package/{lottie-swift/src → Sources}/Private/Utility/Extensions/AnimationKeypathExtension.swift +69 -59
  163. package/Sources/Private/Utility/Extensions/BlendMode+Filter.swift +31 -0
  164. package/Sources/Private/Utility/Extensions/CGColor+RGB.swift +22 -0
  165. package/{lottie-swift/src → Sources}/Private/Utility/Extensions/CGFloatExtensions.swift +70 -67
  166. package/Sources/Private/Utility/Extensions/DataExtension.swift +27 -0
  167. package/Sources/Private/Utility/Extensions/MathKit.swift +450 -0
  168. package/Sources/Private/Utility/Extensions/StringExtensions.swift +38 -0
  169. package/{lottie-swift/src → Sources}/Private/Utility/Helpers/AnimationContext.swift +42 -16
  170. package/Sources/Private/Utility/Interpolatable/InterpolatableExtensions.swift +134 -0
  171. package/{lottie-swift/src → Sources}/Private/Utility/Interpolatable/KeyframeExtensions.swift +13 -10
  172. package/Sources/Private/Utility/Interpolatable/KeyframeGroup+Extensions.swift +59 -0
  173. package/{lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders → Sources/Private/Utility/Interpolatable}/KeyframeInterpolator.swift +125 -108
  174. package/{lottie-swift/src → Sources}/Private/Utility/Primitives/BezierPath.swift +229 -143
  175. package/Sources/Private/Utility/Primitives/CGPointExtension.swift +35 -0
  176. package/{lottie-swift/src → Sources}/Private/Utility/Primitives/ColorExtension.swift +46 -14
  177. package/{lottie-swift/src → Sources}/Private/Utility/Primitives/CompoundBezierPath.swift +58 -49
  178. package/Sources/Private/Utility/Primitives/CurveVertex.swift +184 -0
  179. package/Sources/Private/Utility/Primitives/PathElement.swift +75 -0
  180. package/Sources/Private/Utility/Primitives/UnitBezier.swift +115 -0
  181. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +341 -0
  182. package/Sources/Public/Animation/AnimationPublic.swift +266 -0
  183. package/Sources/Public/Animation/AnimationView.swift +1335 -0
  184. package/Sources/Public/Animation/AnimationViewInitializers.swift +109 -0
  185. package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +22 -0
  186. package/{lottie-swift/src → Sources}/Public/AnimationCache/LRUAnimationCache.swift +22 -18
  187. package/Sources/Public/DynamicProperties/AnimationKeypath.swift +49 -0
  188. package/Sources/Public/DynamicProperties/AnyValueProvider.swift +132 -0
  189. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +54 -36
  190. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +40 -36
  191. package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +123 -0
  192. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +40 -35
  193. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +40 -35
  194. package/{lottie-swift/src → Sources}/Public/FontProvider/AnimationFontProvider.swift +15 -8
  195. package/Sources/Public/ImageProvider/AnimationImageProvider.swift +21 -0
  196. package/Sources/Public/Keyframes/Interpolatable.swift +253 -0
  197. package/Sources/Public/Keyframes/Keyframe.swift +92 -0
  198. package/Sources/Public/Logging/LottieLogger.swift +137 -0
  199. package/Sources/Public/LottieConfiguration.swift +143 -0
  200. package/{lottie-swift/src → Sources}/Public/Primitives/AnimationTime.swift +1 -1
  201. package/{lottie-swift/src → Sources}/Public/Primitives/Color.swift +10 -6
  202. package/{lottie-swift/src → Sources}/Public/Primitives/Vectors.swift +13 -12
  203. package/Sources/Public/TextProvider/AnimationTextProvider.swift +53 -0
  204. package/{lottie-swift/src → Sources}/Public/iOS/AnimatedButton.swift +42 -26
  205. package/{lottie-swift/src → Sources}/Public/iOS/AnimatedControl.swift +100 -91
  206. package/{lottie-swift/src → Sources}/Public/iOS/AnimatedSwitch.swift +128 -94
  207. package/{lottie-swift/src → Sources}/Public/iOS/AnimationSubview.swift +3 -3
  208. package/Sources/Public/iOS/AnimationViewBase.swift +78 -0
  209. package/{lottie-swift/src → Sources}/Public/iOS/BundleImageProvider.swift +36 -34
  210. package/{lottie-swift/src → Sources}/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift +4 -0
  211. package/{lottie-swift/src → Sources}/Public/iOS/Compatibility/CompatibleAnimationView.swift +38 -29
  212. package/{lottie-swift/src → Sources}/Public/iOS/FilepathImageProvider.swift +24 -21
  213. package/{lottie-swift/src → Sources}/Public/iOS/UIColorExtension.swift +4 -4
  214. package/{lottie-swift/src/Public/MacOS/AnimationSubview.swift → Sources/Public/macOS/AnimationSubview.macOS.swift} +4 -5
  215. package/{lottie-swift/src/Public/MacOS/LottieView.swift → Sources/Public/macOS/AnimationViewBase.macOS.swift} +58 -50
  216. package/{lottie-swift/src/Public/MacOS/BundleImageProvider.swift → Sources/Public/macOS/BundleImageProvider.macOS.swift} +31 -32
  217. package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +67 -0
  218. package/Tests/AnimationKeypathTests.swift +103 -0
  219. package/Tests/AutomaticEngineTests.swift +57 -0
  220. package/Tests/BundleTests.swift +25 -0
  221. package/Tests/DataURLTests.swift +64 -0
  222. package/Tests/ParsingTests.swift +43 -0
  223. package/Tests/PerformanceTests.swift +214 -0
  224. package/Tests/Samples/9squares_AlBoardman.json +1 -0
  225. package/Tests/Samples/Boat_Loader.json +1 -0
  226. package/Tests/Samples/HamburgerArrow.json +1 -0
  227. package/Tests/Samples/IconTransitions.json +1 -0
  228. package/Tests/Samples/Images/dog.png +0 -0
  229. package/Tests/Samples/Issues/issue_1125.json +1 -0
  230. package/Tests/Samples/Issues/issue_1260.json +1 -0
  231. package/Tests/Samples/Issues/issue_1403.json +1 -0
  232. package/Tests/Samples/Issues/issue_1407.json +1 -0
  233. package/Tests/Samples/Issues/issue_1460.json +1 -0
  234. package/Tests/Samples/Issues/issue_1488.json +1 -0
  235. package/Tests/Samples/Issues/issue_1505.json +1 -0
  236. package/Tests/Samples/Issues/issue_1541.json +1 -0
  237. package/Tests/Samples/Issues/issue_1557.json +1 -0
  238. package/Tests/Samples/Issues/issue_1603.json +1 -0
  239. package/Tests/Samples/Issues/issue_1628.json +1 -0
  240. package/Tests/Samples/Issues/issue_1636.json +1 -0
  241. package/Tests/Samples/Issues/issue_1643.json +1 -0
  242. package/Tests/Samples/Issues/issue_1655.json +1 -0
  243. package/Tests/Samples/Issues/issue_1664.json +1 -0
  244. package/Tests/Samples/Issues/issue_1683.json +1 -0
  245. package/Tests/Samples/Issues/issue_1687.json +1 -0
  246. package/Tests/Samples/Issues/issue_1711.json +1 -0
  247. package/Tests/Samples/Issues/issue_1717.json +1 -0
  248. package/Tests/Samples/Issues/issue_769.json +1 -0
  249. package/Tests/Samples/Issues/issue_885.json +1 -0
  250. package/Tests/Samples/Issues/issue_965.json +1 -0
  251. package/Tests/Samples/Issues/pr_1536.json +1 -0
  252. package/Tests/Samples/Issues/pr_1563.json +8439 -0
  253. package/Tests/Samples/Issues/pr_1592.json +5527 -0
  254. package/Tests/Samples/Issues/pr_1599.json +738 -0
  255. package/Tests/Samples/Issues/pr_1604_1.json +1 -0
  256. package/Tests/Samples/Issues/pr_1604_2.json +1 -0
  257. package/Tests/Samples/Issues/pr_1632_1.json +1 -0
  258. package/Tests/Samples/Issues/pr_1632_2.json +1 -0
  259. package/Tests/Samples/Issues/pr_1686.json +513 -0
  260. package/Tests/Samples/Issues/pr_1698.json +1 -0
  261. package/Tests/Samples/Issues/pr_1699.json +1 -0
  262. package/Tests/Samples/LottieFiles/LICENSE.md +14 -0
  263. package/Tests/Samples/LottieFiles/bounce_strokes.json +1 -0
  264. package/Tests/Samples/LottieFiles/cactus.json +1 -0
  265. package/Tests/Samples/LottieFiles/dog_car_ride.json +1 -0
  266. package/Tests/Samples/LottieFiles/draft_icon.json +1 -0
  267. package/Tests/Samples/LottieFiles/fireworks.json +1 -0
  268. package/Tests/Samples/LottieFiles/gradient_1.json +1 -0
  269. package/Tests/Samples/LottieFiles/gradient_2.json +1 -0
  270. package/Tests/Samples/LottieFiles/gradient_pill.json +1 -0
  271. package/Tests/Samples/LottieFiles/gradient_shapes.json +1 -0
  272. package/Tests/Samples/LottieFiles/gradient_square.json +1 -0
  273. package/Tests/Samples/LottieFiles/growth.json +1 -0
  274. package/Tests/Samples/LottieFiles/infinity_loader.json +1 -0
  275. package/Tests/Samples/LottieFiles/loading_dots_1.json +1 -0
  276. package/Tests/Samples/LottieFiles/loading_dots_2.json +1 -0
  277. package/Tests/Samples/LottieFiles/loading_dots_3.json +1 -0
  278. package/Tests/Samples/LottieFiles/loading_gradient_strokes.json +1 -0
  279. package/Tests/Samples/LottieFiles/settings_slider.json +1 -0
  280. package/Tests/Samples/LottieFiles/shop.json +1 -0
  281. package/Tests/Samples/LottieFiles/step_loader.json +1 -0
  282. package/Tests/Samples/LottieLogo1.json +1 -0
  283. package/Tests/Samples/LottieLogo1_masked.json +1 -0
  284. package/Tests/Samples/LottieLogo2.json +1 -0
  285. package/Tests/Samples/MotionCorpse_Jrcanest.json +1 -0
  286. package/Tests/Samples/Nonanimating/BasicLayers.json +1 -0
  287. package/Tests/Samples/Nonanimating/DisableNodesTest.json +1 -0
  288. package/Tests/Samples/Nonanimating/FirstText.json +1 -0
  289. package/Tests/Samples/Nonanimating/GeometryTransformTest.json +1 -0
  290. package/Tests/Samples/Nonanimating/Text_AnimatedProperties.json +1 -0
  291. package/Tests/Samples/Nonanimating/Text_Glyph.json +1 -0
  292. package/Tests/Samples/Nonanimating/Text_NoAnimation.json +1 -0
  293. package/Tests/Samples/Nonanimating/Text_NoGlyph.json +1 -0
  294. package/Tests/Samples/Nonanimating/Zoom.json +1 -0
  295. package/Tests/Samples/Nonanimating/_dog.json +1 -0
  296. package/Tests/Samples/Nonanimating/base64Test.json +1 -0
  297. package/Tests/Samples/Nonanimating/blend_mode_test.json +1 -0
  298. package/Tests/Samples/Nonanimating/keypathTest.json +1 -0
  299. package/Tests/Samples/Nonanimating/verifyLineHeight.json +1 -0
  300. package/Tests/Samples/PinJump.json +1 -0
  301. package/Tests/Samples/Switch.json +1 -0
  302. package/Tests/Samples/Switch_States.json +1 -0
  303. package/Tests/Samples/TwitterHeart.json +1 -0
  304. package/Tests/Samples/TwitterHeartButton.json +1 -0
  305. package/Tests/Samples/TypeFace/A.json +1 -0
  306. package/Tests/Samples/TypeFace/Apostrophe.json +1 -0
  307. package/Tests/Samples/TypeFace/B.json +1 -0
  308. package/Tests/Samples/TypeFace/BlinkingCursor.json +1 -0
  309. package/Tests/Samples/TypeFace/C.json +1 -0
  310. package/Tests/Samples/TypeFace/Colon.json +1 -0
  311. package/Tests/Samples/TypeFace/Comma.json +1 -0
  312. package/Tests/Samples/TypeFace/D.json +1 -0
  313. package/Tests/Samples/TypeFace/E.json +1 -0
  314. package/Tests/Samples/TypeFace/F.json +1 -0
  315. package/Tests/Samples/TypeFace/G.json +1 -0
  316. package/Tests/Samples/TypeFace/H.json +1 -0
  317. package/Tests/Samples/TypeFace/I.json +1 -0
  318. package/Tests/Samples/TypeFace/J.json +1 -0
  319. package/Tests/Samples/TypeFace/K.json +1 -0
  320. package/Tests/Samples/TypeFace/L.json +1 -0
  321. package/Tests/Samples/TypeFace/M.json +1 -0
  322. package/Tests/Samples/TypeFace/N.json +1 -0
  323. package/Tests/Samples/TypeFace/O.json +1 -0
  324. package/Tests/Samples/TypeFace/P.json +1 -0
  325. package/Tests/Samples/TypeFace/Q.json +1 -0
  326. package/Tests/Samples/TypeFace/R.json +1 -0
  327. package/Tests/Samples/TypeFace/S.json +1 -0
  328. package/Tests/Samples/TypeFace/T.json +1 -0
  329. package/Tests/Samples/TypeFace/U.json +1 -0
  330. package/Tests/Samples/TypeFace/V.json +1 -0
  331. package/Tests/Samples/TypeFace/W.json +1 -0
  332. package/Tests/Samples/TypeFace/X.json +1 -0
  333. package/Tests/Samples/TypeFace/Y.json +1 -0
  334. package/Tests/Samples/TypeFace/Z.json +1 -0
  335. package/Tests/Samples/Watermelon.json +1 -0
  336. package/Tests/Samples/setValueTest.json +1 -0
  337. package/Tests/Samples/timeremap.json +1 -0
  338. package/Tests/Samples/vcTransition1.json +1 -0
  339. package/Tests/Samples/vcTransition2.json +1 -0
  340. package/Tests/SnapshotConfiguration.swift +158 -0
  341. package/Tests/SnapshotTests.swift +265 -0
  342. package/Tests/Utils/Bundle+Module.swift +30 -0
  343. package/Tests/Utils/HardcodedFontProvider.swift +19 -0
  344. package/Tests/Utils/HardcodedImageProvider.swift +23 -0
  345. package/Tests/Utils/Snapshotting+presentationLayer.swift +47 -0
  346. package/Tests/ValueProvidersTests.swift +27 -0
  347. package/lottie-ios.podspec +12 -12
  348. package/package.json +1 -1
  349. package/script/test-carthage/Cartfile +1 -0
  350. package/script/test-carthage/Cartfile.resolved +1 -0
  351. package/script/test-carthage/CarthageTest/AppDelegate.swift +26 -0
  352. package/script/test-carthage/CarthageTest/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  353. package/script/test-carthage/CarthageTest/Assets.xcassets/AppIcon.appiconset/Contents.json +98 -0
  354. package/script/test-carthage/CarthageTest/Assets.xcassets/Contents.json +6 -0
  355. package/script/test-carthage/CarthageTest/Base.lproj/LaunchScreen.storyboard +25 -0
  356. package/script/test-carthage/CarthageTest/Base.lproj/Main.storyboard +24 -0
  357. package/script/test-carthage/CarthageTest/Info.plist +66 -0
  358. package/script/test-carthage/CarthageTest/SceneDelegate.swift +10 -0
  359. package/script/test-carthage/CarthageTest/ViewController.swift +15 -0
  360. package/script/test-carthage/CarthageTest-macOS/AppDelegate.swift +7 -0
  361. package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  362. package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json +58 -0
  363. package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/Contents.json +6 -0
  364. package/script/test-carthage/CarthageTest-macOS/Base.lproj/Main.storyboard +717 -0
  365. package/script/test-carthage/CarthageTest-macOS/CarthageTest_macOS.entitlements +10 -0
  366. package/script/test-carthage/CarthageTest-macOS/ViewController.swift +15 -0
  367. package/script/test-carthage/CarthageTest.xcodeproj/project.pbxproj +532 -0
  368. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  369. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  370. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  371. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  372. package/script/test-carthage/CarthageTest.xcodeproj/xcuserdata/cal.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  373. package/script/test-carthage/CarthageTest.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +19 -0
  374. package/script/test-carthage/Mintfile +1 -0
  375. package/script/test-spm/LottieSPM.xcworkspace/contents.xcworkspacedata +7 -0
  376. package/script/test-spm/LottieSPM.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  377. package/script/test-spm/Mintfile +1 -0
  378. package/.swiftpm/xcode/package.xcworkspace/xcuserdata/brandonwithrow.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  379. package/Lottie/Info.plist +0 -22
  380. package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/brandonwithrow.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  381. package/Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad/xcschemes/LottieLibraryMacOS.xcscheme +0 -80
  382. package/Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad/xcschemes/xcschememanagement.plist +0 -57
  383. package/lottie-swift/Assets/.gitkeep +0 -0
  384. package/lottie-swift/src/Private/LayerContainers/Utility/LayerFontProvider.swift +0 -37
  385. package/lottie-swift/src/Private/LayerContainers/Utility/LayerTextProvider.swift +0 -36
  386. package/lottie-swift/src/Private/Model/Animation.swift +0 -107
  387. package/lottie-swift/src/Private/Model/Assets/Asset.swift +0 -27
  388. package/lottie-swift/src/Private/Model/Assets/ImageAsset.swift +0 -48
  389. package/lottie-swift/src/Private/Model/Keyframes/Keyframe.swift +0 -128
  390. package/lottie-swift/src/Private/Model/Keyframes/KeyframeGroup.swift +0 -108
  391. package/lottie-swift/src/Private/Model/Layers/LayerModel.swift +0 -150
  392. package/lottie-swift/src/Private/Model/Objects/DashPattern.swift +0 -24
  393. package/lottie-swift/src/Private/Model/Objects/Marker.swift +0 -23
  394. package/lottie-swift/src/Private/Model/Objects/Mask.swift +0 -48
  395. package/lottie-swift/src/Private/Model/Objects/Transform.swift +0 -105
  396. package/lottie-swift/src/Private/Model/ShapeItems/Ellipse.swift +0 -50
  397. package/lottie-swift/src/Private/Model/ShapeItems/FillI.swift +0 -49
  398. package/lottie-swift/src/Private/Model/ShapeItems/GradientFill.swift +0 -86
  399. package/lottie-swift/src/Private/Model/ShapeItems/GradientStroke.swift +0 -125
  400. package/lottie-swift/src/Private/Model/ShapeItems/Group.swift +0 -32
  401. package/lottie-swift/src/Private/Model/ShapeItems/Rectangle.swift +0 -50
  402. package/lottie-swift/src/Private/Model/ShapeItems/Repeater.swift +0 -80
  403. package/lottie-swift/src/Private/Model/ShapeItems/Shape.swift +0 -37
  404. package/lottie-swift/src/Private/Model/ShapeItems/ShapeItem.swift +0 -95
  405. package/lottie-swift/src/Private/Model/ShapeItems/ShapeTransform.swift +0 -68
  406. package/lottie-swift/src/Private/Model/ShapeItems/Star.swift +0 -86
  407. package/lottie-swift/src/Private/Model/ShapeItems/Stroke.swift +0 -67
  408. package/lottie-swift/src/Private/Model/ShapeItems/Trim.swift +0 -53
  409. package/lottie-swift/src/Private/Model/Text/Font.swift +0 -35
  410. package/lottie-swift/src/Private/Model/Text/TextAnimator.swift +0 -99
  411. package/lottie-swift/src/Private/Model/Text/TextDocument.swift +0 -70
  412. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +0 -109
  413. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +0 -132
  414. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +0 -141
  415. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +0 -127
  416. package/lottie-swift/src/Private/Utility/Extensions/MathKit.swift +0 -539
  417. package/lottie-swift/src/Private/Utility/Extensions/StringExtensions.swift +0 -32
  418. package/lottie-swift/src/Private/Utility/Interpolatable/Interpolatable.swift +0 -18
  419. package/lottie-swift/src/Private/Utility/Interpolatable/InterpolatableExtensions.swift +0 -170
  420. package/lottie-swift/src/Private/Utility/Primitives/CurveVertex.swift +0 -177
  421. package/lottie-swift/src/Private/Utility/Primitives/PathElement.swift +0 -68
  422. package/lottie-swift/src/Private/Utility/Primitives/VectorsExtensions.swift +0 -218
  423. package/lottie-swift/src/Public/Animation/AnimationPublic.swift +0 -196
  424. package/lottie-swift/src/Public/Animation/AnimationView.swift +0 -1006
  425. package/lottie-swift/src/Public/Animation/AnimationViewInitializers.swift +0 -83
  426. package/lottie-swift/src/Public/AnimationCache/AnimationCacheProvider.swift +0 -24
  427. package/lottie-swift/src/Public/DynamicProperties/AnimationKeypath.swift +0 -46
  428. package/lottie-swift/src/Public/DynamicProperties/AnyValueProvider.swift +0 -29
  429. package/lottie-swift/src/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +0 -114
  430. package/lottie-swift/src/Public/ImageProvider/AnimationImageProvider.swift +0 -23
  431. package/lottie-swift/src/Public/MacOS/FilepathImageProvider.swift +0 -67
  432. package/lottie-swift/src/Public/TextProvider/AnimationTextProvider.swift +0 -39
  433. package/lottie-swift/src/Public/iOS/LottieView.swift +0 -62
  434. package/lottie-swift-testing.podspec +0 -32
@@ -0,0 +1,1335 @@
1
+ //
2
+ // AnimationView.swift
3
+ // lottie-swift
4
+ //
5
+ // Created by Brandon Withrow on 1/23/19.
6
+ //
7
+
8
+ import Foundation
9
+ import QuartzCore
10
+
11
+ // MARK: - LottieBackgroundBehavior
12
+
13
+ /// Describes the behavior of an AnimationView when the app is moved to the background.
14
+ public enum LottieBackgroundBehavior {
15
+ /// Stop the animation and reset it to the beginning of its current play time. The completion block is called.
16
+ case stop
17
+
18
+ /// Pause the animation in its current state. The completion block is called.
19
+ /// - This is the default when using the Main Thread rendering engine.
20
+ case pause
21
+
22
+ /// Pause the animation and restart it when the application moves to the foreground. The completion block is stored and called when the animation completes.
23
+ case pauseAndRestore
24
+
25
+ /// Stops the animation and sets it to the end of its current play time. The completion block is called.
26
+ case forceFinish
27
+
28
+ /// The animation continues playing in the background.
29
+ /// - This is the default when using the Core Animation rendering engine.
30
+ /// Playing an animation using the Core Animation engine doesn't come with any CPU overhead,
31
+ /// so using `.continuePlaying` avoids the need to stop and then resume the animation
32
+ /// (which does come with some CPU overhead).
33
+ /// - This mode should not be used with the Main Thread rendering engine.
34
+ case continuePlaying
35
+
36
+ // MARK: Public
37
+
38
+ /// The default background behavior, based on the rendering engine being used to play the animation.
39
+ /// - Playing an animation using the Main Thread rendering engine comes with CPU overhead,
40
+ /// so the animation should be paused or stopped when the `AnimationView` is not visible.
41
+ /// - Playing an animation using the Core Animation rendering engine does not come with any
42
+ /// CPU overhead, so these animations do not need to be paused in the background.
43
+ public static func `default`(for renderingEngine: RenderingEngine) -> LottieBackgroundBehavior {
44
+ switch renderingEngine {
45
+ case .mainThread:
46
+ return .pause
47
+ case .coreAnimation:
48
+ return .continuePlaying
49
+ }
50
+ }
51
+ }
52
+
53
+ // MARK: - LottieLoopMode
54
+
55
+ /// Defines animation loop behavior
56
+ public enum LottieLoopMode {
57
+ /// Animation is played once then stops.
58
+ case playOnce
59
+ /// Animation will loop from beginning to end until stopped.
60
+ case loop
61
+ /// Animation will play forward, then backwards and loop until stopped.
62
+ case autoReverse
63
+ /// Animation will loop from beginning to end up to defined amount of times.
64
+ case `repeat`(Float)
65
+ /// Animation will play forward, then backwards a defined amount of times.
66
+ case repeatBackwards(Float)
67
+ }
68
+
69
+ // MARK: Equatable
70
+
71
+ extension LottieLoopMode: Equatable {
72
+ public static func == (lhs: LottieLoopMode, rhs: LottieLoopMode) -> Bool {
73
+ switch (lhs, rhs) {
74
+ case (.repeat(let lhsAmount), .repeat(let rhsAmount)),
75
+ (.repeatBackwards(let lhsAmount), .repeatBackwards(let rhsAmount)):
76
+ return lhsAmount == rhsAmount
77
+ case (.playOnce, .playOnce),
78
+ (.loop, .loop),
79
+ (.autoReverse, .autoReverse):
80
+ return true
81
+ default:
82
+ return false
83
+ }
84
+ }
85
+ }
86
+
87
+ // MARK: - AnimationView
88
+
89
+ @IBDesignable
90
+ final public class AnimationView: AnimationViewBase {
91
+
92
+ // MARK: Lifecycle
93
+
94
+ // MARK: - Public (Initializers)
95
+
96
+ /// Initializes an AnimationView with an animation.
97
+ public init(
98
+ animation: Animation?,
99
+ imageProvider: AnimationImageProvider? = nil,
100
+ textProvider: AnimationTextProvider = DefaultTextProvider(),
101
+ fontProvider: AnimationFontProvider = DefaultFontProvider(),
102
+ configuration: LottieConfiguration = .shared,
103
+ logger: LottieLogger = .shared)
104
+ {
105
+ self.animation = animation
106
+ self.imageProvider = imageProvider ?? BundleImageProvider(bundle: Bundle.main, searchPath: nil)
107
+ self.textProvider = textProvider
108
+ self.fontProvider = fontProvider
109
+ self.configuration = configuration
110
+ self.logger = logger
111
+ super.init(frame: .zero)
112
+ commonInit()
113
+ makeAnimationLayer(usingEngine: configuration.renderingEngine)
114
+ if let animation = animation {
115
+ frame = animation.bounds
116
+ }
117
+ }
118
+
119
+ public init(
120
+ configuration: LottieConfiguration = .shared,
121
+ logger: LottieLogger = .shared)
122
+ {
123
+ animation = nil
124
+ imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil)
125
+ textProvider = DefaultTextProvider()
126
+ fontProvider = DefaultFontProvider()
127
+ self.configuration = configuration
128
+ self.logger = logger
129
+ super.init(frame: .zero)
130
+ commonInit()
131
+ }
132
+
133
+ public override init(frame: CGRect) {
134
+ animation = nil
135
+ imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil)
136
+ textProvider = DefaultTextProvider()
137
+ fontProvider = DefaultFontProvider()
138
+ configuration = .shared
139
+ logger = .shared
140
+ super.init(frame: frame)
141
+ commonInit()
142
+ }
143
+
144
+ required public init?(coder aDecoder: NSCoder) {
145
+ imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil)
146
+ textProvider = DefaultTextProvider()
147
+ fontProvider = DefaultFontProvider()
148
+ configuration = .shared
149
+ logger = .shared
150
+ super.init(coder: aDecoder)
151
+ commonInit()
152
+ }
153
+
154
+ // MARK: Public
155
+
156
+ /// The configuration that this `AnimationView` uses when playing its animation
157
+ public let configuration: LottieConfiguration
158
+
159
+ /// Value Providers that have been registered using `setValueProvider(_:keypath:)`
160
+ public private(set) var valueProviders = [AnimationKeypath: AnyValueProvider]()
161
+
162
+ /// Describes the behavior of an AnimationView when the app is moved to the background.
163
+ ///
164
+ /// The default for the Main Thread animation engine is `pause`,
165
+ /// which pauses the animation when the application moves to
166
+ /// the background. This prevents the animation from consuming CPU
167
+ /// resources when not on-screen. The completion block is called with
168
+ /// `false` for completed.
169
+ ///
170
+ /// The default for the Core Animation engine is `continuePlaying`,
171
+ /// since the Core Animation engine does not have any CPU overhead.
172
+ public var backgroundBehavior: LottieBackgroundBehavior {
173
+ get {
174
+ let currentBackgroundBehavior = _backgroundBehavior ?? .default(for: currentRenderingEngine ?? .mainThread)
175
+
176
+ if
177
+ currentRenderingEngine == .mainThread,
178
+ _backgroundBehavior == .continuePlaying
179
+ {
180
+ logger.assertionFailure("""
181
+ `LottieBackgroundBehavior.continuePlaying` should not be used with the Main Thread
182
+ rendering engine, since this would waste CPU resources on playing an animation
183
+ that is not visible. Consider using a different background mode, or switching to
184
+ the Core Animation rendering engine (which does not have any CPU overhead).
185
+ """)
186
+ }
187
+
188
+ return currentBackgroundBehavior
189
+ }
190
+ set {
191
+ _backgroundBehavior = newValue
192
+ }
193
+ }
194
+
195
+ /// Sets the animation backing the animation view. Setting this will clear the
196
+ /// view's contents, completion blocks and current state. The new animation will
197
+ /// be loaded up and set to the beginning of its timeline.
198
+ public var animation: Animation? {
199
+ didSet {
200
+ makeAnimationLayer(usingEngine: configuration.renderingEngine)
201
+ }
202
+ }
203
+
204
+ /// Sets the image provider for the animation view. An image provider provides the
205
+ /// animation with its required image data.
206
+ ///
207
+ /// Setting this will cause the animation to reload its image contents.
208
+ public var imageProvider: AnimationImageProvider {
209
+ didSet {
210
+ animationLayer?.imageProvider = imageProvider.cachedImageProvider
211
+ reloadImages()
212
+ }
213
+ }
214
+
215
+ /// Sets the text provider for animation view. A text provider provides the
216
+ /// animation with values for text layers
217
+ public var textProvider: AnimationTextProvider {
218
+ didSet {
219
+ animationLayer?.textProvider = textProvider
220
+ }
221
+ }
222
+
223
+ /// Sets the text provider for animation view. A text provider provides the
224
+ /// animation with values for text layers
225
+ public var fontProvider: AnimationFontProvider {
226
+ didSet {
227
+ animationLayer?.fontProvider = fontProvider
228
+ }
229
+ }
230
+
231
+ /// Returns `true` if the animation is currently playing.
232
+ public var isAnimationPlaying: Bool {
233
+ guard let animationLayer = animationLayer else {
234
+ return false
235
+ }
236
+
237
+ if let valueFromLayer = animationLayer.isAnimationPlaying {
238
+ return valueFromLayer
239
+ } else {
240
+ return animationLayer.animation(forKey: activeAnimationName) != nil
241
+ }
242
+ }
243
+
244
+ /// Returns `true` if the animation will start playing when this view is added to a window.
245
+ public var isAnimationQueued: Bool {
246
+ animationContext != nil && waitingToPlayAnimation
247
+ }
248
+
249
+ /// Sets the loop behavior for `play` calls. Defaults to `playOnce`
250
+ public var loopMode: LottieLoopMode = .playOnce {
251
+ didSet {
252
+ updateInFlightAnimation()
253
+ }
254
+ }
255
+
256
+ /// When `true` the animation view will rasterize its contents when not animating.
257
+ /// Rasterizing will improve performance of static animations.
258
+ ///
259
+ /// Note: this will not produce crisp results at resolutions above the animations natural resolution.
260
+ ///
261
+ /// Defaults to `false`
262
+ public var shouldRasterizeWhenIdle = false {
263
+ didSet {
264
+ updateRasterizationState()
265
+ }
266
+ }
267
+
268
+ /// Sets the current animation time with a Progress Time
269
+ ///
270
+ /// Note: Setting this will stop the current animation, if any.
271
+ /// Note 2: If `animation` is nil, setting this will fallback to 0
272
+ public var currentProgress: AnimationProgressTime {
273
+ set {
274
+ if let animation = animation {
275
+ currentFrame = animation.frameTime(forProgress: newValue)
276
+ } else {
277
+ currentFrame = 0
278
+ }
279
+ }
280
+ get {
281
+ if let animation = animation {
282
+ return animation.progressTime(forFrame: currentFrame)
283
+ } else {
284
+ return 0
285
+ }
286
+ }
287
+ }
288
+
289
+ /// Sets the current animation time with a time in seconds.
290
+ ///
291
+ /// Note: Setting this will stop the current animation, if any.
292
+ /// Note 2: If `animation` is nil, setting this will fallback to 0
293
+ public var currentTime: TimeInterval {
294
+ set {
295
+ if let animation = animation {
296
+ currentFrame = animation.frameTime(forTime: newValue)
297
+ } else {
298
+ currentFrame = 0
299
+ }
300
+ }
301
+ get {
302
+ if let animation = animation {
303
+ return animation.time(forFrame: currentFrame)
304
+ } else {
305
+ return 0
306
+ }
307
+ }
308
+ }
309
+
310
+ /// Sets the current animation time with a frame in the animations framerate.
311
+ ///
312
+ /// Note: Setting this will stop the current animation, if any.
313
+ public var currentFrame: AnimationFrameTime {
314
+ set {
315
+ removeCurrentAnimationIfNecessary()
316
+ updateAnimationFrame(newValue)
317
+ }
318
+ get {
319
+ animationLayer?.currentFrame ?? 0
320
+ }
321
+ }
322
+
323
+ /// Returns the current animation frame while an animation is playing.
324
+ public var realtimeAnimationFrame: AnimationFrameTime {
325
+ isAnimationPlaying ? animationLayer?.presentation()?.currentFrame ?? currentFrame : currentFrame
326
+ }
327
+
328
+ /// Returns the current animation frame while an animation is playing.
329
+ public var realtimeAnimationProgress: AnimationProgressTime {
330
+ if let animation = animation {
331
+ return animation.progressTime(forFrame: realtimeAnimationFrame)
332
+ }
333
+ return 0
334
+ }
335
+
336
+ /// Sets the speed of the animation playback. Defaults to 1
337
+ public var animationSpeed: CGFloat = 1 {
338
+ didSet {
339
+ updateInFlightAnimation()
340
+ }
341
+ }
342
+
343
+ /// When `true` the animation will play back at the framerate encoded in the
344
+ /// `Animation` model. When `false` the animation will play at the framerate
345
+ /// of the device.
346
+ ///
347
+ /// Defaults to false
348
+ public var respectAnimationFrameRate = false {
349
+ didSet {
350
+ animationLayer?.respectAnimationFrameRate = respectAnimationFrameRate
351
+ }
352
+ }
353
+
354
+ /// Controls the cropping of an Animation. Setting this property will crop the animation
355
+ /// to the current views bounds by the viewport frame. The coordinate space is specified
356
+ /// in the animation's coordinate space.
357
+ ///
358
+ /// Animatable.
359
+ public var viewportFrame: CGRect? = nil {
360
+ didSet {
361
+ // This is really ugly, but is needed to trigger a layout pass within an animation block.
362
+ // Typically this happens automatically, when layout objects are UIView based.
363
+ // The animation layer is a CALayer which will not implicitly grab the animation
364
+ // duration of a UIView animation block.
365
+ //
366
+ // By setting bounds and then resetting bounds the UIView animation block's
367
+ // duration and curve are captured and added to the layer. This is used in the
368
+ // layout block to animate the animationLayer's position and size.
369
+ let rect = bounds
370
+ self.bounds = CGRect.zero
371
+ self.bounds = rect
372
+ self.setNeedsLayout()
373
+ }
374
+ }
375
+
376
+ override public var intrinsicContentSize: CGSize {
377
+ if let animation = animation {
378
+ return animation.bounds.size
379
+ }
380
+ return .zero
381
+ }
382
+
383
+ /// The rendering engine currently being used by this view.
384
+ /// - This will only be `nil` in cases where the configuration is `automatic`
385
+ /// but a `RootAnimationLayer` hasn't been constructed yet
386
+ public var currentRenderingEngine: RenderingEngine? {
387
+ switch configuration.renderingEngine {
388
+ case .specific(let engine):
389
+ return engine
390
+
391
+ case .automatic:
392
+ guard let animationLayer = animationLayer else {
393
+ return nil
394
+ }
395
+
396
+ if animationLayer is CoreAnimationLayer {
397
+ return .coreAnimation
398
+ } else {
399
+ return .mainThread
400
+ }
401
+ }
402
+ }
403
+
404
+ /// Plays the animation from its current state to the end.
405
+ ///
406
+ /// - Parameter completion: An optional completion closure to be called when the animation completes playing.
407
+ public func play(completion: LottieCompletionBlock? = nil) {
408
+ guard let animation = animation else {
409
+ return
410
+ }
411
+
412
+ /// Build a context for the animation.
413
+ let context = AnimationContext(
414
+ playFrom: CGFloat(animation.startFrame),
415
+ playTo: CGFloat(animation.endFrame),
416
+ closure: completion)
417
+ removeCurrentAnimationIfNecessary()
418
+ addNewAnimationForContext(context)
419
+ }
420
+
421
+ /// Plays the animation from a progress (0-1) to a progress (0-1).
422
+ ///
423
+ /// - Parameter fromProgress: The start progress of the animation. If `nil` the animation will start at the current progress.
424
+ /// - Parameter toProgress: The end progress of the animation.
425
+ /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used.
426
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
427
+ public func play(
428
+ fromProgress: AnimationProgressTime? = nil,
429
+ toProgress: AnimationProgressTime,
430
+ loopMode: LottieLoopMode? = nil,
431
+ completion: LottieCompletionBlock? = nil)
432
+ {
433
+ guard let animation = animation else {
434
+ return
435
+ }
436
+
437
+ removeCurrentAnimationIfNecessary()
438
+ if let loopMode = loopMode {
439
+ /// Set the loop mode, if one was supplied
440
+ self.loopMode = loopMode
441
+ }
442
+ let context = AnimationContext(
443
+ playFrom: animation.frameTime(forProgress: fromProgress ?? currentProgress),
444
+ playTo: animation.frameTime(forProgress: toProgress),
445
+ closure: completion)
446
+ addNewAnimationForContext(context)
447
+ }
448
+
449
+ /// Plays the animation from a start frame to an end frame in the animation's framerate.
450
+ ///
451
+ /// - Parameter fromFrame: The start frame of the animation. If `nil` the animation will start at the current frame.
452
+ /// - Parameter toFrame: The end frame of the animation.
453
+ /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used.
454
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
455
+ public func play(
456
+ fromFrame: AnimationFrameTime? = nil,
457
+ toFrame: AnimationFrameTime,
458
+ loopMode: LottieLoopMode? = nil,
459
+ completion: LottieCompletionBlock? = nil)
460
+ {
461
+ removeCurrentAnimationIfNecessary()
462
+ if let loopMode = loopMode {
463
+ /// Set the loop mode, if one was supplied
464
+ self.loopMode = loopMode
465
+ }
466
+
467
+ let context = AnimationContext(
468
+ playFrom: fromFrame ?? currentFrame,
469
+ playTo: toFrame,
470
+ closure: completion)
471
+ addNewAnimationForContext(context)
472
+ }
473
+
474
+ /// Plays the animation from a named marker to another marker.
475
+ ///
476
+ /// Markers are point in time that are encoded into the Animation data and assigned
477
+ /// a name.
478
+ ///
479
+ /// NOTE: If markers are not found the play command will exit.
480
+ ///
481
+ /// - Parameter fromMarker: The start marker for the animation playback. If `nil` the
482
+ /// animation will start at the current progress.
483
+ /// - Parameter toMarker: The end marker for the animation playback.
484
+ /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used.
485
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
486
+ public func play(
487
+ fromMarker: String? = nil,
488
+ toMarker: String,
489
+ loopMode: LottieLoopMode? = nil,
490
+ completion: LottieCompletionBlock? = nil)
491
+ {
492
+ guard let animation = animation, let markers = animation.markerMap, let to = markers[toMarker] else {
493
+ return
494
+ }
495
+
496
+ removeCurrentAnimationIfNecessary()
497
+ if let loopMode = loopMode {
498
+ /// Set the loop mode, if one was supplied
499
+ self.loopMode = loopMode
500
+ }
501
+
502
+ let fromTime: CGFloat
503
+ if let fromName = fromMarker, let from = markers[fromName] {
504
+ fromTime = CGFloat(from.frameTime)
505
+ } else {
506
+ fromTime = currentFrame
507
+ }
508
+
509
+ let context = AnimationContext(
510
+ playFrom: fromTime,
511
+ playTo: CGFloat(to.frameTime),
512
+ closure: completion)
513
+ addNewAnimationForContext(context)
514
+ }
515
+
516
+ /// Stops the animation and resets the view to its start frame.
517
+ ///
518
+ /// The completion closure will be called with `false`
519
+ public func stop() {
520
+ removeCurrentAnimation()
521
+ currentFrame = 0
522
+ }
523
+
524
+ /// Pauses the animation in its current state.
525
+ ///
526
+ /// The completion closure will be called with `false`
527
+ public func pause() {
528
+ removeCurrentAnimation()
529
+ }
530
+
531
+ /// Reloads the images supplied to the animation from the `imageProvider`
532
+ public func reloadImages() {
533
+ animationLayer?.reloadImages()
534
+ }
535
+
536
+ /// Forces the AnimationView to redraw its contents.
537
+ public func forceDisplayUpdate() {
538
+ animationLayer?.forceDisplayUpdate()
539
+ }
540
+
541
+ /// Sets a ValueProvider for the specified keypath. The value provider will be set
542
+ /// on all properties that match the keypath.
543
+ ///
544
+ /// Nearly all properties of a Lottie animation can be changed at runtime using a
545
+ /// combination of `Animation Keypaths` and `Value Providers`.
546
+ /// Setting a ValueProvider on a keypath will cause the animation to update its
547
+ /// contents and read the new Value Provider.
548
+ ///
549
+ /// A value provider provides a typed value on a frame by frame basis.
550
+ ///
551
+ /// - Parameter valueProvider: The new value provider for the properties.
552
+ /// - Parameter keypath: The keypath used to search for properties.
553
+ ///
554
+ /// Example:
555
+ /// ```
556
+ /// /// A keypath that finds the color value for all `Fill 1` nodes.
557
+ /// let fillKeypath = AnimationKeypath(keypath: "**.Fill 1.Color")
558
+ /// /// A Color Value provider that returns a reddish color.
559
+ /// let redValueProvider = ColorValueProvider(Color(r: 1, g: 0.2, b: 0.3, a: 1))
560
+ /// /// Set the provider on the animationView.
561
+ /// animationView.setValueProvider(redValueProvider, keypath: fillKeypath)
562
+ /// ```
563
+ public func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) {
564
+ guard let animationLayer = animationLayer else { return }
565
+
566
+ valueProviders[keypath] = valueProvider
567
+ animationLayer.setValueProvider(valueProvider, keypath: keypath)
568
+ }
569
+
570
+ /// Reads the value of a property specified by the Keypath.
571
+ /// Returns nil if no property is found.
572
+ ///
573
+ /// - Parameter for: The keypath used to search for the property.
574
+ /// - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used.
575
+ public func getValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? {
576
+ animationLayer?.getValue(for: keypath, atFrame: atFrame)
577
+ }
578
+
579
+ /// Reads the original value of a property specified by the Keypath.
580
+ /// 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.
581
+ /// Returns nil if no property is found.
582
+ ///
583
+ /// - Parameter for: The keypath used to search for the property.
584
+ /// - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used.
585
+ public func getOriginalValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? {
586
+ animationLayer?.getOriginalValue(for: keypath, atFrame: atFrame)
587
+ }
588
+
589
+ /// Logs all child keypaths.
590
+ public func logHierarchyKeypaths() {
591
+ animationLayer?.logHierarchyKeypaths()
592
+ }
593
+
594
+ /// Searches for the nearest child layer to the first Keypath and adds the subview
595
+ /// to that layer. The subview will move and animate with the child layer.
596
+ /// Furthermore the subview will be in the child layers coordinate space.
597
+ ///
598
+ /// Note: if no layer is found for the keypath, then nothing happens.
599
+ ///
600
+ /// - Parameter subview: The subview to add to the found animation layer.
601
+ /// - Parameter keypath: The keypath used to find the animation layer.
602
+ ///
603
+ /// Example:
604
+ /// ```
605
+ /// /// A keypath that finds `Layer 1`
606
+ /// let layerKeypath = AnimationKeypath(keypath: "Layer 1")
607
+ ///
608
+ /// /// Wrap the custom view in an `AnimationSubview`
609
+ /// let subview = AnimationSubview()
610
+ /// subview.addSubview(customView)
611
+ ///
612
+ /// /// Set the provider on the animationView.
613
+ /// animationView.addSubview(subview, forLayerAt: layerKeypath)
614
+ /// ```
615
+ public func addSubview(_ subview: AnimationSubview, forLayerAt keypath: AnimationKeypath) {
616
+ guard let sublayer = animationLayer?.layer(for: keypath) else {
617
+ return
618
+ }
619
+ setNeedsLayout()
620
+ layoutIfNeeded()
621
+ forceDisplayUpdate()
622
+ addSubview(subview)
623
+ if let subViewLayer = subview.viewLayer {
624
+ sublayer.addSublayer(subViewLayer)
625
+ }
626
+ }
627
+
628
+ /// Converts a CGRect from the AnimationView's coordinate space into the
629
+ /// coordinate space of the layer found at Keypath.
630
+ ///
631
+ /// If no layer is found, nil is returned
632
+ ///
633
+ /// - Parameter rect: The CGRect to convert.
634
+ /// - Parameter toLayerAt: The keypath used to find the layer.
635
+ public func convert(_ rect: CGRect, toLayerAt keypath: AnimationKeypath?) -> CGRect? {
636
+ guard let animationLayer = animationLayer else { return nil }
637
+ guard let keypath = keypath else {
638
+ return viewLayer?.convert(rect, to: animationLayer)
639
+ }
640
+ guard let sublayer = animationLayer.layer(for: keypath) else {
641
+ return nil
642
+ }
643
+ setNeedsLayout()
644
+ layoutIfNeeded()
645
+ forceDisplayUpdate()
646
+ return animationLayer.convert(rect, to: sublayer)
647
+ }
648
+
649
+ /// Converts a CGPoint from the AnimationView's coordinate space into the
650
+ /// coordinate space of the layer found at Keypath.
651
+ ///
652
+ /// If no layer is found, nil is returned
653
+ ///
654
+ /// - Parameter point: The CGPoint to convert.
655
+ /// - Parameter toLayerAt: The keypath used to find the layer.
656
+ public func convert(_ point: CGPoint, toLayerAt keypath: AnimationKeypath?) -> CGPoint? {
657
+ guard let animationLayer = animationLayer else { return nil }
658
+ guard let keypath = keypath else {
659
+ return viewLayer?.convert(point, to: animationLayer)
660
+ }
661
+ guard let sublayer = animationLayer.layer(for: keypath) else {
662
+ return nil
663
+ }
664
+ setNeedsLayout()
665
+ layoutIfNeeded()
666
+ forceDisplayUpdate()
667
+ return animationLayer.convert(point, to: sublayer)
668
+ }
669
+
670
+ /// Sets the enabled state of all animator nodes found with the keypath search.
671
+ /// This can be used to interactively enable / disable parts of the animation.
672
+ ///
673
+ /// - Parameter isEnabled: When true the animator nodes affect the rendering tree. When false the node is removed from the tree.
674
+ /// - Parameter keypath: The keypath used to find the node(s).
675
+ public func setNodeIsEnabled(isEnabled: Bool, keypath: AnimationKeypath) {
676
+ guard let animationLayer = animationLayer else { return }
677
+ let nodes = animationLayer.animatorNodes(for: keypath)
678
+ if let nodes = nodes {
679
+ for node in nodes {
680
+ node.isEnabled = isEnabled
681
+ }
682
+ forceDisplayUpdate()
683
+ }
684
+ }
685
+
686
+ /// Markers are a way to describe a point in time by a key name.
687
+ ///
688
+ /// Markers are encoded into animation JSON. By using markers a designer can mark
689
+ /// playback points for a developer to use without having to worry about keeping
690
+ /// track of animation frames. If the animation file is updated, the developer
691
+ /// does not need to update playback code.
692
+ ///
693
+ /// Returns the Progress Time for the marker named. Returns nil if no marker found.
694
+ public func progressTime(forMarker named: String) -> AnimationProgressTime? {
695
+ guard let animation = animation else {
696
+ return nil
697
+ }
698
+ return animation.progressTime(forMarker: named)
699
+ }
700
+
701
+ /// Markers are a way to describe a point in time by a key name.
702
+ ///
703
+ /// Markers are encoded into animation JSON. By using markers a designer can mark
704
+ /// playback points for a developer to use without having to worry about keeping
705
+ /// track of animation frames. If the animation file is updated, the developer
706
+ /// does not need to update playback code.
707
+ ///
708
+ /// Returns the Frame Time for the marker named. Returns nil if no marker found.
709
+ public func frameTime(forMarker named: String) -> AnimationFrameTime? {
710
+ guard let animation = animation else {
711
+ return nil
712
+ }
713
+ return animation.frameTime(forMarker: named)
714
+ }
715
+
716
+ // MARK: Internal
717
+
718
+ var animationLayer: RootAnimationLayer? = nil
719
+
720
+ /// Set animation name from Interface Builder
721
+ @IBInspectable var animationName: String? {
722
+ didSet {
723
+ self.animation = animationName.flatMap {
724
+ Animation.named($0, animationCache: nil)
725
+ }
726
+ }
727
+ }
728
+
729
+ override func layoutAnimation() {
730
+ guard let animation = animation, let animationLayer = animationLayer else { return }
731
+ var position = animation.bounds.center
732
+ let xform: CATransform3D
733
+ var shouldForceUpdates = false
734
+
735
+ if let viewportFrame = viewportFrame {
736
+ shouldForceUpdates = contentMode == .redraw
737
+
738
+ let compAspect = viewportFrame.size.width / viewportFrame.size.height
739
+ let viewAspect = bounds.size.width / bounds.size.height
740
+ let dominantDimension = compAspect > viewAspect ? bounds.size.width : bounds.size.height
741
+ let compDimension = compAspect > viewAspect ? viewportFrame.size.width : viewportFrame.size.height
742
+ let scale = dominantDimension / compDimension
743
+
744
+ let viewportOffset = animation.bounds.center - viewportFrame.center
745
+ xform = CATransform3DTranslate(CATransform3DMakeScale(scale, scale, 1), viewportOffset.x, viewportOffset.y, 0)
746
+ position = bounds.center
747
+ } else {
748
+ switch contentMode {
749
+ case .scaleToFill:
750
+ position = bounds.center
751
+ xform = CATransform3DMakeScale(
752
+ bounds.size.width / animation.size.width,
753
+ bounds.size.height / animation.size.height,
754
+ 1);
755
+ case .scaleAspectFit:
756
+ position = bounds.center
757
+ let compAspect = animation.size.width / animation.size.height
758
+ let viewAspect = bounds.size.width / bounds.size.height
759
+ let dominantDimension = compAspect > viewAspect ? bounds.size.width : bounds.size.height
760
+ let compDimension = compAspect > viewAspect ? animation.size.width : animation.size.height
761
+ let scale = dominantDimension / compDimension
762
+ xform = CATransform3DMakeScale(scale, scale, 1)
763
+ case .scaleAspectFill:
764
+ position = bounds.center
765
+ let compAspect = animation.size.width / animation.size.height
766
+ let viewAspect = bounds.size.width / bounds.size.height
767
+ let scaleWidth = compAspect < viewAspect
768
+ let dominantDimension = scaleWidth ? bounds.size.width : bounds.size.height
769
+ let compDimension = scaleWidth ? animation.size.width : animation.size.height
770
+ let scale = dominantDimension / compDimension
771
+ xform = CATransform3DMakeScale(scale, scale, 1)
772
+ case .redraw:
773
+ shouldForceUpdates = true
774
+ xform = CATransform3DIdentity
775
+ case .center:
776
+ position = bounds.center
777
+ xform = CATransform3DIdentity
778
+ case .top:
779
+ position.x = bounds.center.x
780
+ xform = CATransform3DIdentity
781
+ case .bottom:
782
+ position.x = bounds.center.x
783
+ position.y = bounds.maxY - animation.bounds.midY
784
+ xform = CATransform3DIdentity
785
+ case .left:
786
+ position.y = bounds.center.y
787
+ xform = CATransform3DIdentity
788
+ case .right:
789
+ position.y = bounds.center.y
790
+ position.x = bounds.maxX - animation.bounds.midX
791
+ xform = CATransform3DIdentity
792
+ case .topLeft:
793
+ xform = CATransform3DIdentity
794
+ case .topRight:
795
+ position.x = bounds.maxX - animation.bounds.midX
796
+ xform = CATransform3DIdentity
797
+ case .bottomLeft:
798
+ position.y = bounds.maxY - animation.bounds.midY
799
+ xform = CATransform3DIdentity
800
+ case .bottomRight:
801
+ position.x = bounds.maxX - animation.bounds.midX
802
+ position.y = bounds.maxY - animation.bounds.midY
803
+ xform = CATransform3DIdentity
804
+
805
+ #if os(iOS) || os(tvOS)
806
+ @unknown default:
807
+ logger.assertionFailure("unsupported contentMode: \(contentMode.rawValue)")
808
+ xform = CATransform3DIdentity
809
+ #endif
810
+ }
811
+ }
812
+
813
+ // UIView Animation does not implicitly set CAAnimation time or timing fuctions.
814
+ // If layout is changed in an animation we must get the current animation duration
815
+ // and timing function and then manually create a CAAnimation to match the UIView animation.
816
+ // If layout is changed without animation, explicitly set animation duration to 0.0
817
+ // inside CATransaction to avoid unwanted artifacts.
818
+ /// Check if any animation exist on the view's layer, and match it.
819
+ if let key = viewLayer?.animationKeys()?.first, let animation = viewLayer?.animation(forKey: key) {
820
+ // The layout is happening within an animation block. Grab the animation data.
821
+
822
+ let positionKey = "LayoutPositionAnimation"
823
+ let transformKey = "LayoutTransformAnimation"
824
+ animationLayer.removeAnimation(forKey: positionKey)
825
+ animationLayer.removeAnimation(forKey: transformKey)
826
+
827
+ let positionAnimation = animation.copy() as? CABasicAnimation ?? CABasicAnimation(keyPath: "position")
828
+ positionAnimation.keyPath = "position"
829
+ positionAnimation.isAdditive = false
830
+ positionAnimation.fromValue = (animationLayer.presentation() ?? animationLayer).position
831
+ positionAnimation.toValue = position
832
+ positionAnimation.isRemovedOnCompletion = true
833
+
834
+ let xformAnimation = animation.copy() as? CABasicAnimation ?? CABasicAnimation(keyPath: "transform")
835
+ xformAnimation.keyPath = "transform"
836
+ xformAnimation.isAdditive = false
837
+ xformAnimation.fromValue = (animationLayer.presentation() ?? animationLayer).transform
838
+ xformAnimation.toValue = xform
839
+ xformAnimation.isRemovedOnCompletion = true
840
+
841
+ animationLayer.position = position
842
+ animationLayer.transform = xform
843
+ #if os(OSX)
844
+ animationLayer.anchorPoint = layer?.anchorPoint ?? CGPoint.zero
845
+ #else
846
+ animationLayer.anchorPoint = layer.anchorPoint
847
+ #endif
848
+ animationLayer.add(positionAnimation, forKey: positionKey)
849
+ animationLayer.add(xformAnimation, forKey: transformKey)
850
+ } else {
851
+ // In performance tests, we have to wrap the animation view setup
852
+ // in a `CATransaction` in order for the layers to be deallocated at
853
+ // the correct time. The `CATransaction`s in this method interfere
854
+ // with the ones managed by the performance test, and aren't actually
855
+ // necessary in a headless environment, so we disable them.
856
+ if TestHelpers.performanceTestsAreRunning {
857
+ animationLayer.position = position
858
+ animationLayer.transform = xform
859
+ } else {
860
+ CATransaction.begin()
861
+ CATransaction.setAnimationDuration(0.0)
862
+ CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .linear))
863
+ animationLayer.position = position
864
+ animationLayer.transform = xform
865
+ CATransaction.commit()
866
+ }
867
+ }
868
+
869
+ if shouldForceUpdates {
870
+ animationLayer.forceDisplayUpdate()
871
+ }
872
+ }
873
+
874
+ func updateRasterizationState() {
875
+ if isAnimationPlaying {
876
+ animationLayer?.shouldRasterize = false
877
+ } else {
878
+ animationLayer?.shouldRasterize = shouldRasterizeWhenIdle
879
+ }
880
+ }
881
+
882
+ /// Updates the animation frame. Does not affect any current animations
883
+ func updateAnimationFrame(_ newFrame: CGFloat) {
884
+ // In performance tests, we have to wrap the animation view setup
885
+ // in a `CATransaction` in order for the layers to be deallocated at
886
+ // the correct time. The `CATransaction`s in this method interfere
887
+ // with the ones managed by the performance test, and aren't actually
888
+ // necessary in a headless environment, so we disable them.
889
+ if TestHelpers.performanceTestsAreRunning {
890
+ animationLayer?.currentFrame = newFrame
891
+ animationLayer?.forceDisplayUpdate()
892
+ return
893
+ }
894
+
895
+ CATransaction.begin()
896
+ CATransaction.setCompletionBlock {
897
+ self.animationLayer?.forceDisplayUpdate()
898
+ }
899
+ CATransaction.setDisableActions(true)
900
+ animationLayer?.currentFrame = newFrame
901
+ CATransaction.commit()
902
+ }
903
+
904
+ @objc
905
+ override func animationWillMoveToBackground() {
906
+ updateAnimationForBackgroundState()
907
+ }
908
+
909
+ @objc
910
+ override func animationWillEnterForeground() {
911
+ updateAnimationForForegroundState()
912
+ }
913
+
914
+ override func animationMovedToWindow() {
915
+ /// Don't update any state if the `superview` is `nil`
916
+ /// When A viewA owns superViewB, it removes the superViewB from the window. At this point, viewA still owns superViewB and triggers the viewA method: -didmovetowindow
917
+ guard superview != nil else { return }
918
+
919
+ if window != nil {
920
+ updateAnimationForForegroundState()
921
+ } else {
922
+ updateAnimationForBackgroundState()
923
+ }
924
+ }
925
+
926
+ // MARK: Fileprivate
927
+
928
+ /// Context describing the animation that is currently playing in this `AnimationView`
929
+ /// - When non-nil, an animation is currently playing in this view. Otherwise,
930
+ /// the view is paused on a specific frame.
931
+ fileprivate var animationContext: AnimationContext?
932
+
933
+ fileprivate var _activeAnimationName: String = AnimationView.animationName
934
+ fileprivate var animationID = 0
935
+
936
+ fileprivate var waitingToPlayAnimation = false
937
+
938
+ fileprivate var activeAnimationName: String {
939
+ switch animationLayer?.primaryAnimationKey {
940
+ case .specific(let animationKey):
941
+ return animationKey
942
+ case .managed, nil:
943
+ return _activeAnimationName
944
+ }
945
+ }
946
+
947
+ fileprivate func makeAnimationLayer(usingEngine renderingEngine: RenderingEngineOption) {
948
+ /// Remove current animation if any
949
+ removeCurrentAnimation()
950
+
951
+ if let oldAnimation = animationLayer {
952
+ oldAnimation.removeFromSuperlayer()
953
+ }
954
+
955
+ invalidateIntrinsicContentSize()
956
+
957
+ guard let animation = animation else {
958
+ return
959
+ }
960
+
961
+ let rootAnimationLayer: RootAnimationLayer?
962
+ switch renderingEngine {
963
+ case .automatic:
964
+ rootAnimationLayer = makeAutomaticEngineLayer(for: animation)
965
+ case .specific(.coreAnimation):
966
+ rootAnimationLayer = makeCoreAnimationLayer(for: animation)
967
+ case .specific(.mainThread):
968
+ rootAnimationLayer = makeMainThreadAnimationLayer(for: animation)
969
+ }
970
+
971
+ guard let animationLayer = rootAnimationLayer else {
972
+ return
973
+ }
974
+
975
+ animationLayer.renderScale = screenScale
976
+
977
+ viewLayer?.addSublayer(animationLayer)
978
+ self.animationLayer = animationLayer
979
+ reloadImages()
980
+ animationLayer.setNeedsDisplay()
981
+ setNeedsLayout()
982
+ currentFrame = CGFloat(animation.startFrame)
983
+ }
984
+
985
+ fileprivate func makeMainThreadAnimationLayer(for animation: Animation) -> MainThreadAnimationLayer {
986
+ MainThreadAnimationLayer(
987
+ animation: animation,
988
+ imageProvider: imageProvider.cachedImageProvider,
989
+ textProvider: textProvider,
990
+ fontProvider: fontProvider,
991
+ logger: logger)
992
+ }
993
+
994
+ fileprivate func makeCoreAnimationLayer(for animation: Animation) -> CoreAnimationLayer? {
995
+ do {
996
+ let coreAnimationLayer = try CoreAnimationLayer(
997
+ animation: animation,
998
+ imageProvider: imageProvider.cachedImageProvider,
999
+ fontProvider: fontProvider,
1000
+ compatibilityTrackerMode: .track,
1001
+ logger: logger)
1002
+
1003
+ coreAnimationLayer.didSetUpAnimation = { [logger] compatibilityIssues in
1004
+ logger.assert(
1005
+ compatibilityIssues.isEmpty,
1006
+ "Encountered Core Animation compatibility issues while setting up animation:\n"
1007
+ + compatibilityIssues.map { $0.description }.joined(separator: "\n") + "\n\n"
1008
+ + """
1009
+ This animation cannot be rendered correctly by the Core Animation engine.
1010
+ To resolve this issue, you can use `RenderingEngineOption.automatic`, which automatically falls back
1011
+ to the Main Thread rendering engine when necessary, or just use `RenderingEngineOption.mainThread`.
1012
+
1013
+ """)
1014
+ }
1015
+
1016
+ return coreAnimationLayer
1017
+ } catch {
1018
+ // This should never happen, because we initialize the `CoreAnimationLayer` with
1019
+ // `CompatibilityTracker.Mode.track` (which reports errors in `didSetUpAnimation`,
1020
+ // not by throwing).
1021
+ logger.assertionFailure("Encountered unexpected error \(error)")
1022
+ return nil
1023
+ }
1024
+ }
1025
+
1026
+ fileprivate func makeAutomaticEngineLayer(for animation: Animation) -> CoreAnimationLayer? {
1027
+ do {
1028
+ // Attempt to set up the Core Animation layer. This can either throw immediately in `init`,
1029
+ // or throw an error later in `CALayer.display()` that will be reported in `didSetUpAnimation`.
1030
+ let coreAnimationLayer = try CoreAnimationLayer(
1031
+ animation: animation,
1032
+ imageProvider: imageProvider.cachedImageProvider,
1033
+ fontProvider: fontProvider,
1034
+ compatibilityTrackerMode: .abort,
1035
+ logger: logger)
1036
+
1037
+ coreAnimationLayer.didSetUpAnimation = { [weak self] issues in
1038
+ self?.automaticEngineLayerDidSetUpAnimation(issues)
1039
+ }
1040
+
1041
+ return coreAnimationLayer
1042
+ } catch {
1043
+ if case CompatibilityTracker.Error.encounteredCompatibilityIssue(let compatibilityIssue) = error {
1044
+ automaticEngineLayerDidSetUpAnimation([compatibilityIssue])
1045
+ } else {
1046
+ // This should never happen, because we expect `CoreAnimationLayer` to only throw
1047
+ // `CompatibilityTracker.Error.encounteredCompatibilityIssue` errors.
1048
+ logger.assertionFailure("Encountered unexpected error \(error)")
1049
+ automaticEngineLayerDidSetUpAnimation([])
1050
+ }
1051
+
1052
+ return nil
1053
+ }
1054
+ }
1055
+
1056
+ // Handles any compatibility issues with the Core Animation engine
1057
+ // by falling back to the Main Thread engine
1058
+ fileprivate func automaticEngineLayerDidSetUpAnimation(_ compatibilityIssues: [CompatibilityIssue]) {
1059
+ // If there weren't any compatibility issues, then there's nothing else to do
1060
+ if compatibilityIssues.isEmpty {
1061
+ return
1062
+ }
1063
+
1064
+ logger.warn(
1065
+ "Encountered Core Animation compatibility issue while setting up animation:\n"
1066
+ + compatibilityIssues.map { $0.description }.joined(separator: "\n") + "\n"
1067
+ + """
1068
+ This animation may have additional compatibility issues, but animation setup was cancelled early to avoid wasted work.
1069
+
1070
+ Automatically falling back to Main Thread rendering engine. This fallback comes with some additional performance
1071
+ overhead, which can be reduced by manually specifying that this animation should always use the Main Thread engine.
1072
+
1073
+ """)
1074
+
1075
+ let animationContext = self.animationContext
1076
+ let currentFrame = self.currentFrame
1077
+
1078
+ makeAnimationLayer(usingEngine: .mainThread)
1079
+
1080
+ // Set up the Main Thread animation layer using the same configuration that
1081
+ // was being used by the previous Core Animation layer
1082
+ self.currentFrame = currentFrame
1083
+
1084
+ if let animationContext = animationContext {
1085
+ // `AnimationContext.closure` (`AnimationCompletionDelegate`) is a reference type
1086
+ // that is the animation layer's `CAAnimationDelegate`, and holds a reference to
1087
+ // the animation layer. Reusing a single instance across different animation layers
1088
+ // can cause the animation setup to fail, so we create a copy of the `animationContext`:
1089
+ addNewAnimationForContext(AnimationContext(
1090
+ playFrom: animationContext.playFrom,
1091
+ playTo: animationContext.playTo,
1092
+ closure: animationContext.closure.completionBlock))
1093
+ }
1094
+ }
1095
+
1096
+ fileprivate func updateAnimationForBackgroundState() {
1097
+ if let currentContext = animationContext {
1098
+ switch backgroundBehavior {
1099
+ case .stop:
1100
+ removeCurrentAnimation()
1101
+ updateAnimationFrame(currentContext.playFrom)
1102
+ case .pause:
1103
+ removeCurrentAnimation()
1104
+ case .pauseAndRestore:
1105
+ currentContext.closure.ignoreDelegate = true
1106
+ removeCurrentAnimation()
1107
+ /// Keep the stale context around for when the app enters the foreground.
1108
+ animationContext = currentContext
1109
+ case .forceFinish:
1110
+ removeCurrentAnimation()
1111
+ updateAnimationFrame(currentContext.playTo)
1112
+ case .continuePlaying:
1113
+ break
1114
+ }
1115
+ }
1116
+ }
1117
+
1118
+ fileprivate func updateAnimationForForegroundState() {
1119
+ if let currentContext = animationContext {
1120
+ if waitingToPlayAnimation {
1121
+ waitingToPlayAnimation = false
1122
+ addNewAnimationForContext(currentContext)
1123
+ } else if backgroundBehavior == .pauseAndRestore {
1124
+ /// Restore animation from saved state
1125
+ updateInFlightAnimation()
1126
+ }
1127
+ }
1128
+ }
1129
+
1130
+ /// Removes the current animation and pauses the animation at the current frame
1131
+ /// if necessary before setting up a new animation.
1132
+ /// - This is not necessary with the Core Animation engine, and skipping
1133
+ /// this step lets us avoid building the animations twice (once paused
1134
+ /// and once again playing)
1135
+ /// - This method should only be called immediately before setting up another
1136
+ /// animation -- otherwise this AnimationView could be put in an inconsistent state.
1137
+ fileprivate func removeCurrentAnimationIfNecessary() {
1138
+ switch currentRenderingEngine {
1139
+ case .mainThread:
1140
+ removeCurrentAnimation()
1141
+ case .coreAnimation, nil:
1142
+ // We still need to remove the `animationContext`, since it should only be present
1143
+ // when an animation is actually playing. Without this calling `removeCurrentAnimationIfNecessary()`
1144
+ // and then setting the animation to a specific paused frame would put this
1145
+ // `AnimationView` in an inconsistent state.
1146
+ animationContext = nil
1147
+ }
1148
+ }
1149
+
1150
+ /// Stops the current in flight animation and freezes the animation in its current state.
1151
+ fileprivate func removeCurrentAnimation() {
1152
+ guard animationContext != nil else { return }
1153
+ let pauseFrame = realtimeAnimationFrame
1154
+ animationLayer?.removeAnimation(forKey: activeAnimationName)
1155
+ updateAnimationFrame(pauseFrame)
1156
+ animationContext = nil
1157
+ }
1158
+
1159
+ /// Updates an in flight animation.
1160
+ fileprivate func updateInFlightAnimation() {
1161
+ guard let animationContext = animationContext else { return }
1162
+
1163
+ guard animationContext.closure.animationState != .complete else {
1164
+ // Tried to re-add an already completed animation. Cancel.
1165
+ self.animationContext = nil
1166
+ return
1167
+ }
1168
+
1169
+ /// Tell existing context to ignore its closure
1170
+ animationContext.closure.ignoreDelegate = true
1171
+
1172
+ /// Make a new context, stealing the completion block from the previous.
1173
+ let newContext = AnimationContext(
1174
+ playFrom: animationContext.playFrom,
1175
+ playTo: animationContext.playTo,
1176
+ closure: animationContext.closure.completionBlock)
1177
+
1178
+ /// Remove current animation, and freeze the current frame.
1179
+ let pauseFrame = realtimeAnimationFrame
1180
+ animationLayer?.removeAnimation(forKey: activeAnimationName)
1181
+ animationLayer?.currentFrame = pauseFrame
1182
+
1183
+ addNewAnimationForContext(newContext)
1184
+ }
1185
+
1186
+ /// Adds animation to animation layer and sets the delegate. If animation layer or animation are nil, exits.
1187
+ fileprivate func addNewAnimationForContext(_ animationContext: AnimationContext) {
1188
+ guard let animationlayer = animationLayer, let animation = animation else {
1189
+ return
1190
+ }
1191
+
1192
+ self.animationContext = animationContext
1193
+
1194
+ switch currentRenderingEngine {
1195
+ case .mainThread:
1196
+ guard window != nil else {
1197
+ waitingToPlayAnimation = true
1198
+ return
1199
+ }
1200
+
1201
+ case .coreAnimation, nil:
1202
+ // The Core Animation engine automatically batches animation setup to happen
1203
+ // in `CALayer.display()`, which won't be called until the layer is on-screen,
1204
+ // so we don't need to defer animation setup at this layer.
1205
+ break
1206
+ }
1207
+
1208
+ animationID = animationID + 1
1209
+ _activeAnimationName = AnimationView.animationName + String(animationID)
1210
+
1211
+ if let coreAnimationLayer = animationlayer as? CoreAnimationLayer {
1212
+ var animationContext = animationContext
1213
+
1214
+ // Core Animation doesn't natively support negative speed values,
1215
+ // so instead we can swap `playFrom` / `playTo`
1216
+ if animationSpeed < 0 {
1217
+ let temp = animationContext.playFrom
1218
+ animationContext.playFrom = animationContext.playTo
1219
+ animationContext.playTo = temp
1220
+ }
1221
+
1222
+ var timingConfiguration = CoreAnimationLayer.CAMediaTimingConfiguration(
1223
+ autoreverses: loopMode.caAnimationConfiguration.autoreverses,
1224
+ repeatCount: loopMode.caAnimationConfiguration.repeatCount,
1225
+ speed: abs(Float(animationSpeed)))
1226
+
1227
+ // The animation should start playing from the `currentFrame`,
1228
+ // if `currentFrame` is included in the time range being played.
1229
+ let lowerBoundTime = min(animationContext.playFrom, animationContext.playTo)
1230
+ let upperBoundTime = max(animationContext.playFrom, animationContext.playTo)
1231
+ if (lowerBoundTime ..< upperBoundTime).contains(round(currentFrame)) {
1232
+ // We have to configure this differently depending on the loop mode:
1233
+ switch loopMode {
1234
+ // When playing exactly once (and not looping), we can just set the
1235
+ // `playFrom` time to be the `currentFrame`. Since the animation duration
1236
+ // is based on `playFrom` and `playTo`, this automatically truncates the
1237
+ // duration (so the animation stops playing at `playFrom`).
1238
+ case .playOnce:
1239
+ animationContext.playFrom = currentFrame
1240
+
1241
+ // When looping, we specifically _don't_ want to affect the duration of the animation,
1242
+ // since that would affect the duration of all subsequent loops. We just want to adjust
1243
+ // the duration of the _first_ loop. Instead of setting `playFrom`, we just add a `timeOffset`
1244
+ // so the first loop begins at `currentTime` but all subsequent loops are the standard duration.
1245
+ default:
1246
+ if animationSpeed < 0 {
1247
+ timingConfiguration.timeOffset = animation.time(forFrame: animationContext.playFrom) - currentTime
1248
+ } else {
1249
+ timingConfiguration.timeOffset = currentTime - animation.time(forFrame: animationContext.playFrom)
1250
+ }
1251
+ }
1252
+ }
1253
+
1254
+ coreAnimationLayer.playAnimation(configuration: .init(
1255
+ animationContext: animationContext,
1256
+ timingConfiguration: timingConfiguration))
1257
+
1258
+ return
1259
+ }
1260
+
1261
+ /// At this point there is no animation on animationLayer and its state is set.
1262
+
1263
+ let framerate = animation.framerate
1264
+
1265
+ let playFrom = animationContext.playFrom.clamp(animation.startFrame, animation.endFrame)
1266
+ let playTo = animationContext.playTo.clamp(animation.startFrame, animation.endFrame)
1267
+
1268
+ let duration = ((max(playFrom, playTo) - min(playFrom, playTo)) / CGFloat(framerate))
1269
+
1270
+ let playingForward: Bool =
1271
+ (
1272
+ (animationSpeed > 0 && playFrom < playTo) ||
1273
+ (animationSpeed < 0 && playTo < playFrom))
1274
+
1275
+ var startFrame = currentFrame.clamp(min(playFrom, playTo), max(playFrom, playTo))
1276
+ if startFrame == playTo {
1277
+ startFrame = playFrom
1278
+ }
1279
+
1280
+ let timeOffset: TimeInterval = playingForward
1281
+ ? Double(startFrame - min(playFrom, playTo)) / framerate
1282
+ : Double(max(playFrom, playTo) - startFrame) / framerate
1283
+
1284
+ let layerAnimation = CABasicAnimation(keyPath: "currentFrame")
1285
+ layerAnimation.fromValue = playFrom
1286
+ layerAnimation.toValue = playTo
1287
+ layerAnimation.speed = Float(animationSpeed)
1288
+ layerAnimation.duration = TimeInterval(duration)
1289
+ layerAnimation.fillMode = CAMediaTimingFillMode.both
1290
+ layerAnimation.repeatCount = loopMode.caAnimationConfiguration.repeatCount
1291
+ layerAnimation.autoreverses = loopMode.caAnimationConfiguration.autoreverses
1292
+
1293
+ layerAnimation.isRemovedOnCompletion = false
1294
+ if timeOffset != 0 {
1295
+ let currentLayerTime = viewLayer?.convertTime(CACurrentMediaTime(), from: nil) ?? 0
1296
+ layerAnimation.beginTime = currentLayerTime - (timeOffset * 1 / Double(abs(animationSpeed)))
1297
+ }
1298
+ layerAnimation.delegate = animationContext.closure
1299
+ animationContext.closure.animationLayer = animationlayer
1300
+ animationContext.closure.animationKey = activeAnimationName
1301
+
1302
+ animationlayer.add(layerAnimation, forKey: activeAnimationName)
1303
+ updateRasterizationState()
1304
+ }
1305
+
1306
+ // MARK: Private
1307
+
1308
+ static private let animationName = "Lottie"
1309
+
1310
+ private let logger: LottieLogger
1311
+
1312
+ /// The `LottieBackgroundBehavior` that was specified manually by setting `self.backgroundBehavior`
1313
+ private var _backgroundBehavior: LottieBackgroundBehavior?
1314
+
1315
+ }
1316
+
1317
+ // MARK: - LottieLoopMode + caAnimationConfiguration
1318
+
1319
+ extension LottieLoopMode {
1320
+ /// The `CAAnimation` configuration that reflects this mode
1321
+ var caAnimationConfiguration: (repeatCount: Float, autoreverses: Bool) {
1322
+ switch self {
1323
+ case .playOnce:
1324
+ return (repeatCount: 1, autoreverses: false)
1325
+ case .loop:
1326
+ return (repeatCount: .greatestFiniteMagnitude, autoreverses: false)
1327
+ case .autoReverse:
1328
+ return (repeatCount: .greatestFiniteMagnitude, autoreverses: true)
1329
+ case .repeat(let amount):
1330
+ return (repeatCount: amount, autoreverses: false)
1331
+ case .repeatBackwards(let amount):
1332
+ return (repeatCount: amount, autoreverses: true)
1333
+ }
1334
+ }
1335
+ }