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,447 @@
1
+ // Created by Cal Stephens on 12/14/21.
2
+ // Copyright © 2021 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ extension CALayer {
7
+
8
+ // MARK: Internal
9
+
10
+ /// Constructs a `CAKeyframeAnimation` that reflects the given keyframes,
11
+ /// and adds it to this `CALayer`.
12
+ @nonobjc
13
+ func addAnimation<KeyframeValue, ValueRepresentation: Equatable>(
14
+ for property: LayerProperty<ValueRepresentation>,
15
+ keyframes: ContiguousArray<Keyframe<KeyframeValue>>,
16
+ value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation,
17
+ context: LayerAnimationContext)
18
+ throws
19
+ {
20
+ if let customAnimation = try customizedAnimation(for: property, context: context) {
21
+ add(customAnimation, timedWith: context)
22
+ }
23
+
24
+ else if
25
+ let defaultAnimation = try defaultAnimation(
26
+ for: property,
27
+ keyframes: keyframes,
28
+ value: keyframeValueMapping,
29
+ context: context)
30
+ {
31
+ let timedAnimation = defaultAnimation.timed(with: context, for: self)
32
+ add(timedAnimation, forKey: property.caLayerKeypath)
33
+ }
34
+ }
35
+
36
+ // MARK: Private
37
+
38
+ /// Constructs a `CAAnimation` that reflects the given keyframes
39
+ /// - If the value can be applied directly to the CALayer using KVC,
40
+ /// then no `CAAnimation` will be created and the value will be applied directly.
41
+ @nonobjc
42
+ private func defaultAnimation<KeyframeValue, ValueRepresentation>(
43
+ for property: LayerProperty<ValueRepresentation>,
44
+ keyframes: ContiguousArray<Keyframe<KeyframeValue>>,
45
+ value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation,
46
+ context: LayerAnimationContext)
47
+ throws
48
+ -> CAAnimation?
49
+ {
50
+ guard !keyframes.isEmpty else { return nil }
51
+
52
+ // If there is exactly one keyframe value, we can improve performance
53
+ // by applying that value directly to the layer instead of creating
54
+ // a relatively expensive `CAKeyframeAnimation`.
55
+ if keyframes.count == 1 {
56
+ return singleKeyframeAnimation(
57
+ for: property,
58
+ keyframeValue: try keyframeValueMapping(keyframes[0].value),
59
+ writeDirectlyToPropertyIfPossible: true)
60
+ }
61
+
62
+ // Split the keyframes into segments with the same `CAAnimationCalculationMode` value
63
+ // - Each of these segments will become their own `CAKeyframeAnimation`
64
+ let animationSegments = keyframes.segmentsSplitByCalculationMode()
65
+
66
+ // If we only have a single segment, we can just create a single `CAKeyframeAnimation`
67
+ // instead of wrapping it in a `CAAnimationGroup` -- this reduces allocation overhead a bit.
68
+ if animationSegments.count == 1 {
69
+ return try keyframeAnimation(
70
+ for: property,
71
+ keyframes: animationSegments[0],
72
+ value: keyframeValueMapping,
73
+ context: context)
74
+ } else {
75
+ return try animationGroup(
76
+ for: property,
77
+ animationSegments: animationSegments,
78
+ value: keyframeValueMapping,
79
+ context: context)
80
+ }
81
+ }
82
+
83
+ /// A `CAAnimation` that applies the custom value from the `AnyValueProvider`
84
+ /// registered for this specific property's `AnimationKeypath`,
85
+ /// if one has been registered using `AnimationView.setValueProvider(_:keypath:)`.
86
+ @nonobjc
87
+ private func customizedAnimation<ValueRepresentation>(
88
+ for property: LayerProperty<ValueRepresentation>,
89
+ context: LayerAnimationContext)
90
+ throws
91
+ -> CAPropertyAnimation?
92
+ {
93
+ guard
94
+ let customizableProperty = property.customizableProperty,
95
+ let customKeyframes = try context.valueProviderStore.customKeyframes(
96
+ of: customizableProperty,
97
+ for: AnimationKeypath(keys: context.currentKeypath.keys + customizableProperty.name.map { $0.rawValue }),
98
+ context: context)
99
+ else { return nil }
100
+
101
+ // Since custom animations are overriding an existing animation,
102
+ // we always have to create a CAAnimation and can't write directly
103
+ // to the layer property
104
+ if
105
+ customKeyframes.keyframes.count == 1,
106
+ let singleKeyframeAnimation = singleKeyframeAnimation(
107
+ for: property,
108
+ keyframeValue: customKeyframes.keyframes[0].value,
109
+ writeDirectlyToPropertyIfPossible: false)
110
+ {
111
+ return singleKeyframeAnimation
112
+ }
113
+
114
+ return try keyframeAnimation(
115
+ for: property,
116
+ keyframes: Array(customKeyframes.keyframes),
117
+ value: { $0 },
118
+ context: context)
119
+ }
120
+
121
+ /// Creates an animation that applies a single keyframe to this layer property
122
+ /// - In many cases this animation can be omitted entirely, and the underlying
123
+ /// property can be set directly. In that case, no animation will be created.
124
+ private func singleKeyframeAnimation<ValueRepresentation>(
125
+ for property: LayerProperty<ValueRepresentation>,
126
+ keyframeValue: ValueRepresentation,
127
+ writeDirectlyToPropertyIfPossible: Bool)
128
+ -> CABasicAnimation?
129
+ {
130
+ if writeDirectlyToPropertyIfPossible {
131
+ // If the keyframe value is the same as the layer's default value for this property,
132
+ // then we can just ignore this set of keyframes.
133
+ if keyframeValue == property.defaultValue {
134
+ return nil
135
+ }
136
+
137
+ // If the property on the CALayer being animated hasn't been modified from the default yet,
138
+ // then we can apply the keyframe value directly to the layer using KVC instead
139
+ // of creating a `CAAnimation`.
140
+ if
141
+ let defaultValue = property.defaultValue,
142
+ defaultValue == value(forKey: property.caLayerKeypath) as? ValueRepresentation
143
+ {
144
+ setValue(keyframeValue, forKeyPath: property.caLayerKeypath)
145
+ return nil
146
+ }
147
+ }
148
+
149
+ // Otherwise, we still need to create a `CAAnimation`, but we can
150
+ // create a simple `CABasicAnimation` that is still less expensive
151
+ // than computing a `CAKeyframeAnimation`.
152
+ let animation = CABasicAnimation(keyPath: property.caLayerKeypath)
153
+ animation.fromValue = keyframeValue
154
+ animation.toValue = keyframeValue
155
+ return animation
156
+ }
157
+
158
+ /// Creates a `CAAnimationGroup` that wraps a `CAKeyframeAnimation` for each
159
+ /// of the given `animationSegments`
160
+ private func animationGroup<KeyframeValue, ValueRepresentation>(
161
+ for property: LayerProperty<ValueRepresentation>,
162
+ animationSegments: [[Keyframe<KeyframeValue>]],
163
+ value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation,
164
+ context: LayerAnimationContext)
165
+ throws
166
+ -> CAAnimationGroup
167
+ {
168
+ // Build the `CAKeyframeAnimation` for each segment of keyframes
169
+ // with the same `CAAnimationCalculationMode`.
170
+ // - Here we have a non-zero number of animation segments,
171
+ // all of which have a non-zero number of keyframes.
172
+ let segmentAnimations: [CAKeyframeAnimation] = try animationSegments.indices.map { index in
173
+ let animationSegment = animationSegments[index]
174
+ var segmentStartTime = context.time(for: animationSegment.first!.time)
175
+ var segmentEndTime = context.time(for: animationSegment.last!.time)
176
+
177
+ // Every portion of the animation timeline has to be covered by a `CAKeyframeAnimation`,
178
+ // so if this is the first or last segment then the start/end time should be exactly
179
+ // the start/end time of the animation itself.
180
+ let isFirstSegment = (index == animationSegments.indices.first!)
181
+ let isLastSegment = (index == animationSegments.indices.last!)
182
+
183
+ if isFirstSegment {
184
+ segmentStartTime = context.time(for: context.animation.startFrame)
185
+ }
186
+
187
+ if isLastSegment {
188
+ segmentEndTime = context.time(for: context.animation.endFrame)
189
+ }
190
+
191
+ let segmentDuration = segmentEndTime - segmentStartTime
192
+
193
+ // We're building `CAKeyframeAnimation`s, so the `keyTimes` are expressed
194
+ // relative to 0 (`segmentStartTime`) and 1 (`segmentEndTime`). This is different
195
+ // from the default behavior of the `keyframeAnimation` method, where times
196
+ // are expressed relative to the entire animation duration.
197
+ let customKeyTimes = animationSegment.map { keyframeModel -> NSNumber in
198
+ let keyframeTime = context.time(for: keyframeModel.time)
199
+ let segmentProgressTime = ((keyframeTime - segmentStartTime) / segmentDuration)
200
+ return segmentProgressTime as NSNumber
201
+ }
202
+
203
+ let animation = try keyframeAnimation(
204
+ for: property,
205
+ keyframes: animationSegment,
206
+ value: keyframeValueMapping,
207
+ customKeyTimes: customKeyTimes,
208
+ context: context)
209
+
210
+ animation.duration = segmentDuration
211
+ animation.beginTime = segmentStartTime
212
+ return animation
213
+ }
214
+
215
+ let fullAnimation = CAAnimationGroup()
216
+ fullAnimation.animations = segmentAnimations
217
+ return fullAnimation
218
+ }
219
+
220
+ /// Creates and validates a `CAKeyframeAnimation` for the given keyframes
221
+ private func keyframeAnimation<KeyframeValue, ValueRepresentation>(
222
+ for property: LayerProperty<ValueRepresentation>,
223
+ keyframes: [Keyframe<KeyframeValue>],
224
+ value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation,
225
+ customKeyTimes: [NSNumber]? = nil,
226
+ context: LayerAnimationContext)
227
+ throws
228
+ -> CAKeyframeAnimation
229
+ {
230
+ // Convert the list of `Keyframe<T>` into
231
+ // the representation used by `CAKeyframeAnimation`
232
+ var keyTimes = customKeyTimes ?? keyframes.map { keyframeModel -> NSNumber in
233
+ NSNumber(value: Float(context.progressTime(for: keyframeModel.time)))
234
+ }
235
+
236
+ var timingFunctions = self.timingFunctions(for: keyframes)
237
+ let calculationMode = self.calculationMode(for: keyframes)
238
+
239
+ let animation = CAKeyframeAnimation(keyPath: property.caLayerKeypath)
240
+
241
+ // Position animations define a `CGPath` curve that should be followed,
242
+ // instead of animating directly between keyframe point values.
243
+ if property.caLayerKeypath == LayerProperty<CGPoint>.position.caLayerKeypath {
244
+ animation.path = try path(keyframes: keyframes, value: { value in
245
+ guard let point = try keyframeValueMapping(value) as? CGPoint else {
246
+ context.logger.assertionFailure("Cannot create point from keyframe with value \(value)")
247
+ return .zero
248
+ }
249
+
250
+ return point
251
+ })
252
+ }
253
+
254
+ // All other types of keyframes provide individual values that are interpolated by Core Animation
255
+ else {
256
+ var values = try keyframes.map { keyframeModel in
257
+ try keyframeValueMapping(keyframeModel.value)
258
+ }
259
+
260
+ validate(
261
+ values: &values,
262
+ keyTimes: &keyTimes,
263
+ timingFunctions: &timingFunctions,
264
+ for: calculationMode,
265
+ context: context)
266
+
267
+ animation.values = values
268
+ }
269
+
270
+ animation.calculationMode = calculationMode
271
+ animation.keyTimes = keyTimes
272
+ animation.timingFunctions = timingFunctions
273
+ return animation
274
+ }
275
+
276
+ /// The `CAAnimationCalculationMode` that should be used for a `CAKeyframeAnimation`
277
+ /// animating the given keyframes
278
+ private func calculationMode<KeyframeValue>(
279
+ for keyframes: [Keyframe<KeyframeValue>])
280
+ -> CAAnimationCalculationMode
281
+ {
282
+ // At this point we expect all of the animations to have been split in
283
+ // to segments based on the `CAAnimationCalculationMode`, so we can just
284
+ // check the first keyframe.
285
+ if keyframes[0].isHold {
286
+ return .discrete
287
+ } else {
288
+ return .linear
289
+ }
290
+ }
291
+
292
+ /// `timingFunctions` to apply to a `CAKeyframeAnimation` animating the given keyframes
293
+ private func timingFunctions<KeyframeValue>(
294
+ for keyframes: [Keyframe<KeyframeValue>])
295
+ -> [CAMediaTimingFunction]
296
+ {
297
+ // Compute the timing function between each keyframe and the subsequent keyframe
298
+ var timingFunctions: [CAMediaTimingFunction] = []
299
+
300
+ for (index, keyframe) in keyframes.enumerated()
301
+ where index != keyframes.indices.last
302
+ {
303
+ let nextKeyframe = keyframes[index + 1]
304
+
305
+ let controlPoint1 = keyframe.outTangent?.pointValue ?? .zero
306
+ let controlPoint2 = nextKeyframe.inTangent?.pointValue ?? CGPoint(x: 1, y: 1)
307
+
308
+ timingFunctions.append(CAMediaTimingFunction(
309
+ controlPoints:
310
+ Float(controlPoint1.x),
311
+ Float(controlPoint1.y),
312
+ Float(controlPoint2.x),
313
+ Float(controlPoint2.y)))
314
+ }
315
+
316
+ return timingFunctions
317
+ }
318
+
319
+ /// Creates a `CGPath` for the given `position` keyframes,
320
+ /// which accounts for `spatialInTangent`s and `spatialOutTangents`
321
+ private func path<KeyframeValue>(
322
+ keyframes positionKeyframes: [Keyframe<KeyframeValue>],
323
+ value keyframeValueMapping: (KeyframeValue) throws -> CGPoint) rethrows
324
+ -> CGPath
325
+ {
326
+ let path = CGMutablePath()
327
+
328
+ for (index, keyframe) in positionKeyframes.enumerated() {
329
+ if index == positionKeyframes.indices.first {
330
+ path.move(to: try keyframeValueMapping(keyframe.value))
331
+ }
332
+
333
+ if index != positionKeyframes.indices.last {
334
+ let nextKeyframe = positionKeyframes[index + 1]
335
+
336
+ if
337
+ let controlPoint1 = keyframe.spatialOutTangent?.pointValue,
338
+ let controlPoint2 = nextKeyframe.spatialInTangent?.pointValue,
339
+ !(controlPoint1 == .zero && controlPoint2 == .zero)
340
+ {
341
+ path.addCurve(
342
+ to: try keyframeValueMapping(nextKeyframe.value),
343
+ control1: try keyframeValueMapping(keyframe.value) + controlPoint1,
344
+ control2: try keyframeValueMapping(nextKeyframe.value) + controlPoint2)
345
+ }
346
+
347
+ else {
348
+ path.addLine(to: try keyframeValueMapping(nextKeyframe.value))
349
+ }
350
+ }
351
+ }
352
+
353
+ path.closeSubpath()
354
+ return path
355
+ }
356
+
357
+ /// Validates that the requirements of the `CAKeyframeAnimation` API are met correctly
358
+ private func validate<ValueRepresentation>(
359
+ values: inout [ValueRepresentation],
360
+ keyTimes: inout [NSNumber],
361
+ timingFunctions: inout [CAMediaTimingFunction],
362
+ for calculationMode: CAAnimationCalculationMode,
363
+ context: LayerAnimationContext)
364
+ {
365
+ // Validate that we have correct start (0.0) and end (1.0) keyframes.
366
+ // From the documentation of `CAKeyframeAnimation.keyTimes`:
367
+ // - The first value in the `keyTimes` array must be 0.0 and the last value must be 1.0.
368
+ if keyTimes.first != 0.0 {
369
+ keyTimes.insert(0.0, at: 0)
370
+ values.insert(values[0], at: 0)
371
+ timingFunctions.insert(CAMediaTimingFunction(name: .linear), at: 0)
372
+ }
373
+
374
+ if keyTimes.last != 1.0 {
375
+ keyTimes.append(1.0)
376
+ values.append(values.last!)
377
+ timingFunctions.append(CAMediaTimingFunction(name: .linear))
378
+ }
379
+
380
+ switch calculationMode {
381
+ case .linear, .cubic:
382
+ // From the documentation of `CAKeyframeAnimation.keyTimes`:
383
+ // - The number of elements in the keyTimes array
384
+ // should match the number of elements in the values property
385
+ context.logger.assert(
386
+ values.count == keyTimes.count,
387
+ "`values.count` must exactly equal `keyTimes.count`")
388
+
389
+ context.logger.assert(
390
+ timingFunctions.count == (values.count - 1),
391
+ "`timingFunctions.count` must exactly equal `values.count - 1`")
392
+
393
+ case .discrete:
394
+ // From the documentation of `CAKeyframeAnimation.keyTimes`:
395
+ // - If the calculationMode is set to discrete... the keyTimes array
396
+ // should have one more entry than appears in the values array.
397
+ values.removeLast()
398
+
399
+ context.logger.assert(
400
+ keyTimes.count == values.count + 1,
401
+ "`keyTimes.count` must exactly equal `values.count + 1`")
402
+
403
+ default:
404
+ context.logger.assertionFailure("""
405
+ Unexpected keyframe calculation mode \(calculationMode)
406
+ """)
407
+ }
408
+ }
409
+
410
+ }
411
+
412
+ extension RandomAccessCollection {
413
+ /// Splits this array of `Keyframe`s into segments with the same `CAAnimationCalculationMode`
414
+ /// - Keyframes with `isHold=true` become `discrete`, and keyframes with `isHold=false`
415
+ /// become linear. Each `CAKeyframeAnimation` can only be one or the other, so each
416
+ /// `calculationModeSegment` becomes its own `CAKeyframeAnimation`.
417
+ func segmentsSplitByCalculationMode<KeyframeValue>() -> [[Element]]
418
+ where Element == Keyframe<KeyframeValue>, Index == Int
419
+ {
420
+ var segments: [[Element]] = []
421
+ var currentSegment: [Element] = []
422
+
423
+ for keyframe in self {
424
+ guard let mostRecentKeyframe = currentSegment.last else {
425
+ currentSegment.append(keyframe)
426
+ continue
427
+ }
428
+
429
+ // When `isHold` changes between any two given keyframes, we have to create a new segment
430
+ if keyframe.isHold != mostRecentKeyframe.isHold {
431
+ // Add this keyframe to both the existing segment that is ending,
432
+ // so we know how long that segment is, and the new segment,
433
+ // so we know when that segment starts.
434
+ currentSegment.append(keyframe)
435
+ segments.append(currentSegment)
436
+ currentSegment = [keyframe]
437
+ }
438
+
439
+ else {
440
+ currentSegment.append(keyframe)
441
+ }
442
+ }
443
+
444
+ segments.append(currentSegment)
445
+ return segments
446
+ }
447
+ }
@@ -0,0 +1,81 @@
1
+ // Created by Cal Stephens on 1/28/22.
2
+ // Copyright © 2022 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ extension CAShapeLayer {
7
+ /// Adds animations for the given `CombinedShapeItem` to this `CALayer`
8
+ @nonobjc
9
+ func addAnimations(
10
+ for combinedShapes: CombinedShapeItem,
11
+ context: LayerAnimationContext,
12
+ pathMultiplier: PathMultiplier)
13
+ throws
14
+ {
15
+ try addAnimation(
16
+ for: .path,
17
+ keyframes: combinedShapes.shapes.keyframes,
18
+ value: { paths in
19
+ let combinedPath = CGMutablePath()
20
+ for path in paths {
21
+ combinedPath.addPath(path.cgPath().duplicated(times: pathMultiplier))
22
+ }
23
+ return combinedPath
24
+ },
25
+ context: context)
26
+ }
27
+ }
28
+
29
+ // MARK: - CombinedShapeItem
30
+
31
+ /// A custom `ShapeItem` subclass that combines multiple `Shape`s into a single `KeyframeGroup`
32
+ final class CombinedShapeItem: ShapeItem {
33
+
34
+ // MARK: Lifecycle
35
+
36
+ init(shapes: KeyframeGroup<[BezierPath]>, name: String) {
37
+ self.shapes = shapes
38
+ super.init(name: name, type: .shape, hidden: false)
39
+ }
40
+
41
+ required init(from _: Decoder) throws {
42
+ fatalError("init(from:) has not been implemented")
43
+ }
44
+
45
+ required init(dictionary _: [String: Any]) throws {
46
+ fatalError("init(dictionary:) has not been implemented")
47
+ }
48
+
49
+ // MARK: Internal
50
+
51
+ let shapes: KeyframeGroup<[BezierPath]>
52
+
53
+ }
54
+
55
+ extension CombinedShapeItem {
56
+ /// Manually combines the given shape keyframes by manually interpolating at each frame
57
+ static func manuallyInterpolating(
58
+ shapes: [KeyframeGroup<BezierPath>],
59
+ name: String,
60
+ context: LayerContext)
61
+ -> CombinedShapeItem
62
+ {
63
+ let animationTimeRange = Int(context.animation.startFrame)...Int(context.animation.endFrame)
64
+
65
+ let interpolators = shapes.map { shape in
66
+ KeyframeInterpolator(keyframes: shape.keyframes)
67
+ }
68
+
69
+ let interpolatedKeyframes = animationTimeRange.map { frame in
70
+ Keyframe(
71
+ value: interpolators.compactMap { interpolator in
72
+ interpolator.value(frame: AnimationFrameTime(frame)) as? BezierPath
73
+ },
74
+ time: AnimationFrameTime(frame))
75
+ }
76
+
77
+ return CombinedShapeItem(
78
+ shapes: KeyframeGroup(keyframes: ContiguousArray(interpolatedKeyframes)),
79
+ name: name)
80
+ }
81
+ }
@@ -0,0 +1,41 @@
1
+ // Created by Cal Stephens on 12/21/21.
2
+ // Copyright © 2021 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ extension CAShapeLayer {
7
+ /// Adds animations for the given `BezierPath` keyframes to this `CALayer`
8
+ @nonobjc
9
+ func addAnimations(
10
+ for customPath: KeyframeGroup<BezierPath>,
11
+ context: LayerAnimationContext,
12
+ pathMultiplier: PathMultiplier = 1,
13
+ transformPath: (CGPath) -> CGPath = { $0 })
14
+ throws
15
+ {
16
+ try addAnimation(
17
+ for: .path,
18
+ keyframes: customPath.keyframes,
19
+ value: { pathKeyframe in
20
+ transformPath(pathKeyframe.cgPath().duplicated(times: pathMultiplier))
21
+ },
22
+ context: context)
23
+ }
24
+ }
25
+
26
+ extension CGPath {
27
+ /// Duplicates this `CGPath` so that it is repeated the given number of times
28
+ func duplicated(times: Int) -> CGPath {
29
+ if times <= 1 {
30
+ return self
31
+ }
32
+
33
+ let cgPath = CGMutablePath()
34
+
35
+ for _ in 0..<times {
36
+ cgPath.addPath(self)
37
+ }
38
+
39
+ return cgPath
40
+ }
41
+ }
@@ -0,0 +1,55 @@
1
+ // Created by Cal Stephens on 12/21/21.
2
+ // Copyright © 2021 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ extension CAShapeLayer {
7
+ /// Adds animations for the given `Ellipse` to this `CALayer`
8
+ @nonobjc
9
+ func addAnimations(
10
+ for ellipse: Ellipse,
11
+ context: LayerAnimationContext,
12
+ pathMultiplier: PathMultiplier)
13
+ throws
14
+ {
15
+ try addAnimation(
16
+ for: .path,
17
+ keyframes: ellipse.combinedKeyframes(context: context).keyframes,
18
+ value: { keyframe in
19
+ BezierPath.ellipse(
20
+ size: keyframe.size.sizeValue,
21
+ center: keyframe.position.pointValue,
22
+ direction: ellipse.direction)
23
+ .cgPath()
24
+ .duplicated(times: pathMultiplier)
25
+ },
26
+ context: context)
27
+ }
28
+ }
29
+
30
+ extension Ellipse {
31
+ /// Data that represents how to render an ellipse at a specific point in time
32
+ struct Keyframe {
33
+ let size: Vector3D
34
+ let position: Vector3D
35
+ }
36
+
37
+ /// Creates a single array of animatable keyframes from the separate arrays of keyframes in this Ellipse
38
+ func combinedKeyframes(context: LayerAnimationContext) throws-> KeyframeGroup<Ellipse.Keyframe> {
39
+ let combinedKeyframes = Keyframes.combinedIfPossible(
40
+ size, position,
41
+ makeCombinedResult: Ellipse.Keyframe.init)
42
+
43
+ if let combinedKeyframes = combinedKeyframes {
44
+ return combinedKeyframes
45
+ } else {
46
+ // If we weren't able to combine all of the keyframes, we have to take the timing values
47
+ // from one property and use a fixed value for the other properties.
48
+ return try size.map { sizeValue in
49
+ Keyframe(
50
+ size: sizeValue,
51
+ position: try position.exactlyOneKeyframe(context: context, description: "ellipse position"))
52
+ }
53
+ }
54
+ }
55
+ }