lottie-ios 3.2.2 → 3.4.1

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 (316) 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/.spi.yml +6 -0
  5. package/.swift-version +1 -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/xcuserdata/{brandonwithrow.xcuserdatad → cal.xcuserdatad}/xcschemes/xcschememanagement.plist +25 -15
  9. package/.swiftpm/xcode/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  10. package/Gemfile +4 -0
  11. package/Gemfile.lock +102 -0
  12. package/Lottie.xcodeproj/project.pbxproj +2003 -1972
  13. package/Lottie.xcodeproj/project.xcworkspace/contents.xcworkspacedata +1 -1
  14. package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  15. package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_iOS.xcscheme → Lottie (iOS).xcscheme } +15 -18
  16. package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_macOS.xcscheme → Lottie (macOS).xcscheme } +7 -20
  17. package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_tvOS.xcscheme → Lottie (tvOS).xcscheme } +5 -18
  18. package/Lottie.xcodeproj/xcuserdata/cal.xcuserdatad/xcschemes/xcschememanagement.plist +37 -0
  19. package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +24 -0
  20. package/Lottie.xcworkspace/contents.xcworkspacedata +10 -0
  21. package/Lottie.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  22. package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +115 -0
  23. package/Lottie.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  24. package/{Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad → Lottie.xcworkspace/xcuserdata/cal.xcuserdatad}/xcdebugger/Breakpoints_v2.xcbkptlist +2 -2
  25. package/Lottie.xcworkspace/xcuserdata/cal.xcuserdatad/xcdebugger/Expressions.xcexplist +153 -0
  26. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  27. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +39 -0
  28. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +25 -0
  29. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +30 -0
  30. package/Package.resolved +88 -0
  31. package/Package.swift +10 -15
  32. package/README.md +40 -60
  33. package/Rakefile +121 -0
  34. package/Sources/Private/CoreAnimation/Animations/CAAnimation+TimingConfiguration.swift +81 -0
  35. package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +448 -0
  36. package/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift +53 -0
  37. package/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift +41 -0
  38. package/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift +28 -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 +31 -0
  43. package/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift +246 -0
  44. package/Sources/Private/CoreAnimation/Animations/StarAnimation.swift +95 -0
  45. package/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift +70 -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 +492 -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 +73 -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 +87 -0
  56. package/Sources/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift +131 -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 +60 -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/ShapeItemLayer.swift +302 -0
  63. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +319 -0
  64. package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +47 -0
  65. package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +91 -0
  66. package/Sources/Private/CoreAnimation/Layers/TransformLayer.swift +11 -0
  67. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +139 -0
  68. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/CompositionLayer.swift +93 -85
  69. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/ImageCompositionLayer.swift +24 -21
  70. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/MaskContainerLayer.swift +75 -54
  71. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/NullCompositionLayer.swift +5 -5
  72. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/PreCompositionLayer.swift +61 -44
  73. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/ShapeCompositionLayer.swift +22 -20
  74. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/SolidCompositionLayer.swift +26 -17
  75. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/TextCompositionLayer.swift +42 -36
  76. package/{lottie-swift/src/Private/LayerContainers/AnimationContainer.swift → Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift} +200 -140
  77. package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +47 -0
  78. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/CompositionLayersInitializer.swift +33 -24
  79. package/{lottie-swift/src/Private/LayerContainers/Utility/TextLayer.swift → Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift} +120 -106
  80. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/InvertedMatteLayer.swift +25 -24
  81. package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +41 -0
  82. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/LayerImageProvider.swift +20 -16
  83. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +40 -0
  84. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/LayerTransformNode.swift +82 -67
  85. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Extensions/ItemsExtension.swift +4 -4
  86. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/NodeProperty.swift +29 -21
  87. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift +16 -10
  88. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift +6 -6
  89. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift +5 -5
  90. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +10 -8
  91. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueContainer.swift +25 -21
  92. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift +23 -17
  93. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift +125 -108
  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 +242 -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 +54 -42
  112. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +65 -57
  113. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +84 -58
  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 +197 -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 +75 -0
  141. package/Sources/Private/Model/ShapeItems/Fill.swift +74 -0
  142. package/Sources/Private/Model/ShapeItems/GradientFill.swift +124 -0
  143. package/Sources/Private/Model/ShapeItems/GradientStroke.swift +186 -0
  144. package/Sources/Private/Model/ShapeItems/Group.swift +48 -0
  145. package/{lottie-swift/src → Sources}/Private/Model/ShapeItems/Merge.swift +29 -11
  146. package/Sources/Private/Model/ShapeItems/Rectangle.swift +73 -0
  147. package/Sources/Private/Model/ShapeItems/Repeater.swift +136 -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 +136 -0
  151. package/Sources/Private/Model/ShapeItems/Star.swift +132 -0
  152. package/Sources/Private/Model/ShapeItems/Stroke.swift +102 -0
  153. package/Sources/Private/Model/ShapeItems/Trim.swift +78 -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 → Sources}/Private/Utility/Primitives/BezierPath.swift +229 -143
  174. package/Sources/Private/Utility/Primitives/CGPointExtension.swift +35 -0
  175. package/{lottie-swift/src → Sources}/Private/Utility/Primitives/ColorExtension.swift +46 -14
  176. package/{lottie-swift/src → Sources}/Private/Utility/Primitives/CompoundBezierPath.swift +58 -49
  177. package/Sources/Private/Utility/Primitives/CurveVertex.swift +184 -0
  178. package/Sources/Private/Utility/Primitives/PathElement.swift +75 -0
  179. package/Sources/Private/Utility/Primitives/UnitBezier.swift +115 -0
  180. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +341 -0
  181. package/Sources/Public/Animation/AnimationPublic.swift +266 -0
  182. package/Sources/Public/Animation/AnimationView.swift +1335 -0
  183. package/Sources/Public/Animation/AnimationViewInitializers.swift +109 -0
  184. package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +22 -0
  185. package/{lottie-swift/src → Sources}/Public/AnimationCache/LRUAnimationCache.swift +22 -18
  186. package/Sources/Public/DynamicProperties/AnimationKeypath.swift +49 -0
  187. package/Sources/Public/DynamicProperties/AnyValueProvider.swift +132 -0
  188. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +54 -36
  189. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +40 -36
  190. package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +123 -0
  191. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +40 -35
  192. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +40 -35
  193. package/{lottie-swift/src → Sources}/Public/FontProvider/AnimationFontProvider.swift +15 -8
  194. package/Sources/Public/ImageProvider/AnimationImageProvider.swift +21 -0
  195. package/Sources/Public/Keyframes/Interpolatable.swift +253 -0
  196. package/Sources/Public/Keyframes/Keyframe.swift +92 -0
  197. package/Sources/Public/Logging/LottieLogger.swift +137 -0
  198. package/Sources/Public/LottieConfiguration.swift +143 -0
  199. package/{lottie-swift/src → Sources}/Public/Primitives/AnimationTime.swift +1 -1
  200. package/{lottie-swift/src → Sources}/Public/Primitives/Color.swift +10 -6
  201. package/{lottie-swift/src → Sources}/Public/Primitives/Vectors.swift +13 -12
  202. package/Sources/Public/TextProvider/AnimationTextProvider.swift +53 -0
  203. package/{lottie-swift/src → Sources}/Public/iOS/AnimatedButton.swift +42 -26
  204. package/{lottie-swift/src → Sources}/Public/iOS/AnimatedControl.swift +100 -91
  205. package/{lottie-swift/src → Sources}/Public/iOS/AnimatedSwitch.swift +128 -94
  206. package/{lottie-swift/src → Sources}/Public/iOS/AnimationSubview.swift +3 -3
  207. package/Sources/Public/iOS/AnimationViewBase.swift +78 -0
  208. package/{lottie-swift/src → Sources}/Public/iOS/BundleImageProvider.swift +36 -34
  209. package/{lottie-swift/src → Sources}/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift +4 -0
  210. package/{lottie-swift/src → Sources}/Public/iOS/Compatibility/CompatibleAnimationView.swift +38 -29
  211. package/{lottie-swift/src → Sources}/Public/iOS/FilepathImageProvider.swift +24 -21
  212. package/{lottie-swift/src → Sources}/Public/iOS/UIColorExtension.swift +4 -4
  213. package/{lottie-swift/src/Public/MacOS/AnimationSubview.swift → Sources/Public/macOS/AnimationSubview.macOS.swift} +4 -5
  214. package/{lottie-swift/src/Public/MacOS/LottieView.swift → Sources/Public/macOS/AnimationViewBase.macOS.swift} +58 -50
  215. package/{lottie-swift/src/Public/MacOS/BundleImageProvider.swift → Sources/Public/macOS/BundleImageProvider.macOS.swift} +31 -32
  216. package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +67 -0
  217. package/Tests/AnimationKeypathTests.swift +94 -0
  218. package/Tests/AutomaticEngineTests.swift +57 -0
  219. package/Tests/BundleTests.swift +25 -0
  220. package/Tests/DataURLTests.swift +64 -0
  221. package/Tests/ParsingTests.swift +43 -0
  222. package/Tests/PerformanceTests.swift +215 -0
  223. package/Tests/SnapshotConfiguration.swift +153 -0
  224. package/Tests/SnapshotTests.swift +265 -0
  225. package/Tests/Utils/Bundle+Module.swift +30 -0
  226. package/Tests/Utils/HardcodedFontProvider.swift +19 -0
  227. package/Tests/Utils/HardcodedImageProvider.swift +23 -0
  228. package/Tests/Utils/Snapshotting+presentationLayer.swift +47 -0
  229. package/Tests/ValueProvidersTests.swift +27 -0
  230. package/lottie-ios.podspec +11 -12
  231. package/package.json +1 -1
  232. package/script/test-carthage/Cartfile +1 -0
  233. package/script/test-carthage/Cartfile.resolved +1 -0
  234. package/script/test-carthage/CarthageTest/AppDelegate.swift +26 -0
  235. package/script/test-carthage/CarthageTest/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  236. package/script/test-carthage/CarthageTest/Assets.xcassets/AppIcon.appiconset/Contents.json +98 -0
  237. package/script/test-carthage/CarthageTest/Assets.xcassets/Contents.json +6 -0
  238. package/script/test-carthage/CarthageTest/Base.lproj/LaunchScreen.storyboard +25 -0
  239. package/script/test-carthage/CarthageTest/Base.lproj/Main.storyboard +24 -0
  240. package/script/test-carthage/CarthageTest/Info.plist +66 -0
  241. package/script/test-carthage/CarthageTest/SceneDelegate.swift +10 -0
  242. package/script/test-carthage/CarthageTest/ViewController.swift +15 -0
  243. package/script/test-carthage/CarthageTest-macOS/AppDelegate.swift +7 -0
  244. package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  245. package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json +58 -0
  246. package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/Contents.json +6 -0
  247. package/script/test-carthage/CarthageTest-macOS/Base.lproj/Main.storyboard +717 -0
  248. package/script/test-carthage/CarthageTest-macOS/CarthageTest_macOS.entitlements +10 -0
  249. package/script/test-carthage/CarthageTest-macOS/ViewController.swift +15 -0
  250. package/script/test-carthage/CarthageTest.xcodeproj/project.pbxproj +532 -0
  251. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  252. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  253. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  254. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  255. package/script/test-carthage/CarthageTest.xcodeproj/xcuserdata/cal.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  256. package/script/test-carthage/CarthageTest.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +19 -0
  257. package/script/test-carthage/Mintfile +1 -0
  258. package/script/test-spm/LottieSPM.xcworkspace/contents.xcworkspacedata +7 -0
  259. package/script/test-spm/LottieSPM.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  260. package/script/test-spm/Mintfile +1 -0
  261. package/.swiftpm/xcode/package.xcworkspace/xcuserdata/brandonwithrow.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  262. package/Lottie/Info.plist +0 -22
  263. package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/brandonwithrow.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  264. package/Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad/xcschemes/LottieLibraryMacOS.xcscheme +0 -80
  265. package/Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad/xcschemes/xcschememanagement.plist +0 -57
  266. package/lottie-swift/Assets/.gitkeep +0 -0
  267. package/lottie-swift/src/Private/LayerContainers/Utility/LayerFontProvider.swift +0 -37
  268. package/lottie-swift/src/Private/LayerContainers/Utility/LayerTextProvider.swift +0 -36
  269. package/lottie-swift/src/Private/Model/Animation.swift +0 -107
  270. package/lottie-swift/src/Private/Model/Assets/Asset.swift +0 -27
  271. package/lottie-swift/src/Private/Model/Assets/ImageAsset.swift +0 -48
  272. package/lottie-swift/src/Private/Model/Keyframes/Keyframe.swift +0 -128
  273. package/lottie-swift/src/Private/Model/Keyframes/KeyframeGroup.swift +0 -108
  274. package/lottie-swift/src/Private/Model/Layers/LayerModel.swift +0 -150
  275. package/lottie-swift/src/Private/Model/Objects/DashPattern.swift +0 -24
  276. package/lottie-swift/src/Private/Model/Objects/Marker.swift +0 -23
  277. package/lottie-swift/src/Private/Model/Objects/Mask.swift +0 -48
  278. package/lottie-swift/src/Private/Model/Objects/Transform.swift +0 -105
  279. package/lottie-swift/src/Private/Model/ShapeItems/Ellipse.swift +0 -50
  280. package/lottie-swift/src/Private/Model/ShapeItems/FillI.swift +0 -49
  281. package/lottie-swift/src/Private/Model/ShapeItems/GradientFill.swift +0 -86
  282. package/lottie-swift/src/Private/Model/ShapeItems/GradientStroke.swift +0 -125
  283. package/lottie-swift/src/Private/Model/ShapeItems/Group.swift +0 -32
  284. package/lottie-swift/src/Private/Model/ShapeItems/Rectangle.swift +0 -50
  285. package/lottie-swift/src/Private/Model/ShapeItems/Repeater.swift +0 -80
  286. package/lottie-swift/src/Private/Model/ShapeItems/Shape.swift +0 -37
  287. package/lottie-swift/src/Private/Model/ShapeItems/ShapeItem.swift +0 -95
  288. package/lottie-swift/src/Private/Model/ShapeItems/ShapeTransform.swift +0 -68
  289. package/lottie-swift/src/Private/Model/ShapeItems/Star.swift +0 -86
  290. package/lottie-swift/src/Private/Model/ShapeItems/Stroke.swift +0 -67
  291. package/lottie-swift/src/Private/Model/ShapeItems/Trim.swift +0 -53
  292. package/lottie-swift/src/Private/Model/Text/Font.swift +0 -35
  293. package/lottie-swift/src/Private/Model/Text/TextAnimator.swift +0 -99
  294. package/lottie-swift/src/Private/Model/Text/TextDocument.swift +0 -70
  295. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +0 -109
  296. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +0 -132
  297. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +0 -141
  298. package/lottie-swift/src/Private/Utility/Extensions/MathKit.swift +0 -539
  299. package/lottie-swift/src/Private/Utility/Extensions/StringExtensions.swift +0 -32
  300. package/lottie-swift/src/Private/Utility/Interpolatable/Interpolatable.swift +0 -18
  301. package/lottie-swift/src/Private/Utility/Interpolatable/InterpolatableExtensions.swift +0 -170
  302. package/lottie-swift/src/Private/Utility/Primitives/CurveVertex.swift +0 -177
  303. package/lottie-swift/src/Private/Utility/Primitives/PathElement.swift +0 -68
  304. package/lottie-swift/src/Private/Utility/Primitives/VectorsExtensions.swift +0 -218
  305. package/lottie-swift/src/Public/Animation/AnimationPublic.swift +0 -196
  306. package/lottie-swift/src/Public/Animation/AnimationView.swift +0 -1006
  307. package/lottie-swift/src/Public/Animation/AnimationViewInitializers.swift +0 -83
  308. package/lottie-swift/src/Public/AnimationCache/AnimationCacheProvider.swift +0 -24
  309. package/lottie-swift/src/Public/DynamicProperties/AnimationKeypath.swift +0 -46
  310. package/lottie-swift/src/Public/DynamicProperties/AnyValueProvider.swift +0 -29
  311. package/lottie-swift/src/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +0 -114
  312. package/lottie-swift/src/Public/ImageProvider/AnimationImageProvider.swift +0 -23
  313. package/lottie-swift/src/Public/MacOS/FilepathImageProvider.swift +0 -67
  314. package/lottie-swift/src/Public/TextProvider/AnimationTextProvider.swift +0 -39
  315. package/lottie-swift/src/Public/iOS/LottieView.swift +0 -62
  316. package/lottie-swift-testing.podspec +0 -32
@@ -0,0 +1,448 @@
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,
340
+ controlPoint2 != .zero
341
+ {
342
+ path.addCurve(
343
+ to: try keyframeValueMapping(nextKeyframe.value),
344
+ control1: try keyframeValueMapping(keyframe.value) + controlPoint1,
345
+ control2: try keyframeValueMapping(nextKeyframe.value) + controlPoint2)
346
+ }
347
+
348
+ else {
349
+ path.addLine(to: try keyframeValueMapping(nextKeyframe.value))
350
+ }
351
+ }
352
+ }
353
+
354
+ path.closeSubpath()
355
+ return path
356
+ }
357
+
358
+ /// Validates that the requirements of the `CAKeyframeAnimation` API are met correctly
359
+ private func validate<ValueRepresentation>(
360
+ values: inout [ValueRepresentation],
361
+ keyTimes: inout [NSNumber],
362
+ timingFunctions: inout [CAMediaTimingFunction],
363
+ for calculationMode: CAAnimationCalculationMode,
364
+ context: LayerAnimationContext)
365
+ {
366
+ // Validate that we have correct start (0.0) and end (1.0) keyframes.
367
+ // From the documentation of `CAKeyframeAnimation.keyTimes`:
368
+ // - The first value in the `keyTimes` array must be 0.0 and the last value must be 1.0.
369
+ if keyTimes.first != 0.0 {
370
+ keyTimes.insert(0.0, at: 0)
371
+ values.insert(values[0], at: 0)
372
+ timingFunctions.insert(CAMediaTimingFunction(name: .linear), at: 0)
373
+ }
374
+
375
+ if keyTimes.last != 1.0 {
376
+ keyTimes.append(1.0)
377
+ values.append(values.last!)
378
+ timingFunctions.append(CAMediaTimingFunction(name: .linear))
379
+ }
380
+
381
+ switch calculationMode {
382
+ case .linear, .cubic:
383
+ // From the documentation of `CAKeyframeAnimation.keyTimes`:
384
+ // - The number of elements in the keyTimes array
385
+ // should match the number of elements in the values property
386
+ context.logger.assert(
387
+ values.count == keyTimes.count,
388
+ "`values.count` must exactly equal `keyTimes.count`")
389
+
390
+ context.logger.assert(
391
+ timingFunctions.count == (values.count - 1),
392
+ "`timingFunctions.count` must exactly equal `values.count - 1`")
393
+
394
+ case .discrete:
395
+ // From the documentation of `CAKeyframeAnimation.keyTimes`:
396
+ // - If the calculationMode is set to discrete... the keyTimes array
397
+ // should have one more entry than appears in the values array.
398
+ values.removeLast()
399
+
400
+ context.logger.assert(
401
+ keyTimes.count == values.count + 1,
402
+ "`keyTimes.count` must exactly equal `values.count + 1`")
403
+
404
+ default:
405
+ context.logger.assertionFailure("""
406
+ Unexpected keyframe calculation mode \(calculationMode)
407
+ """)
408
+ }
409
+ }
410
+
411
+ }
412
+
413
+ extension RandomAccessCollection {
414
+ /// Splits this array of `Keyframe`s into segments with the same `CAAnimationCalculationMode`
415
+ /// - Keyframes with `isHold=true` become `discrete`, and keyframes with `isHold=false`
416
+ /// become linear. Each `CAKeyframeAnimation` can only be one or the other, so each
417
+ /// `calculationModeSegment` becomes its own `CAKeyframeAnimation`.
418
+ func segmentsSplitByCalculationMode<KeyframeValue>() -> [[Element]]
419
+ where Element == Keyframe<KeyframeValue>, Index == Int
420
+ {
421
+ var segments: [[Element]] = []
422
+ var currentSegment: [Element] = []
423
+
424
+ for keyframe in self {
425
+ guard let mostRecentKeyframe = currentSegment.last else {
426
+ currentSegment.append(keyframe)
427
+ continue
428
+ }
429
+
430
+ // When `isHold` changes between any two given keyframes, we have to create a new segment
431
+ if keyframe.isHold != mostRecentKeyframe.isHold {
432
+ // Add this keyframe to both the existing segment that is ending,
433
+ // so we know how long that segment is, and the new segment,
434
+ // so we know when that segment starts.
435
+ currentSegment.append(keyframe)
436
+ segments.append(currentSegment)
437
+ currentSegment = [keyframe]
438
+ }
439
+
440
+ else {
441
+ currentSegment.append(keyframe)
442
+ }
443
+ }
444
+
445
+ segments.append(currentSegment)
446
+ return segments
447
+ }
448
+ }
@@ -0,0 +1,53 @@
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
+ }
@@ -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,28 @@
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.size.keyframes,
18
+ value: { sizeKeyframe in
19
+ BezierPath.ellipse(
20
+ size: sizeKeyframe.sizeValue,
21
+ center: try ellipse.position.exactlyOneKeyframe(context: context, description: "ellipse position").value.pointValue,
22
+ direction: ellipse.direction)
23
+ .cgPath()
24
+ .duplicated(times: pathMultiplier)
25
+ },
26
+ context: context)
27
+ }
28
+ }