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,246 @@
1
+ // Created by Cal Stephens on 1/7/22.
2
+ // Copyright © 2022 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ extension CAShapeLayer {
7
+ /// Adds a `path` animation for the given `ShapeItem`
8
+ @nonobjc
9
+ func addAnimations(
10
+ for shape: ShapeItem,
11
+ context: LayerAnimationContext,
12
+ pathMultiplier: PathMultiplier) throws
13
+ {
14
+ switch shape {
15
+ case let customShape as Shape:
16
+ try addAnimations(for: customShape.path, context: context, pathMultiplier: pathMultiplier)
17
+
18
+ case let combinedShape as CombinedShapeItem:
19
+ try addAnimations(for: combinedShape, context: context, pathMultiplier: pathMultiplier)
20
+
21
+ case let ellipse as Ellipse:
22
+ try addAnimations(for: ellipse, context: context, pathMultiplier: pathMultiplier)
23
+
24
+ case let rectangle as Rectangle:
25
+ try addAnimations(for: rectangle, context: context, pathMultiplier: pathMultiplier)
26
+
27
+ case let star as Star:
28
+ try addAnimations(for: star, context: context, pathMultiplier: pathMultiplier)
29
+
30
+ default:
31
+ // None of the other `ShapeItem` subclasses draw a `path`
32
+ try context.logCompatibilityIssue("Unexpected shape type \(type(of: shape))")
33
+ return
34
+ }
35
+ }
36
+
37
+ /// Adds a `fillColor` animation for the given `Fill` object
38
+ @nonobjc
39
+ func addAnimations(for fill: Fill, context: LayerAnimationContext) throws {
40
+ fillRule = fill.fillRule.caFillRule
41
+
42
+ try addAnimation(
43
+ for: .fillColor,
44
+ keyframes: fill.color.keyframes,
45
+ value: \.cgColorValue,
46
+ context: context)
47
+
48
+ try addOpacityAnimation(for: fill, context: context)
49
+ }
50
+
51
+ /// Adds animations for `strokeStart` and `strokeEnd` from the given `Trim` object
52
+ @nonobjc
53
+ func addAnimations(for trim: Trim, context: LayerAnimationContext) throws -> PathMultiplier {
54
+ let (strokeStartKeyframes, strokeEndKeyframes, pathMultiplier) = try trim.caShapeLayerKeyframes(context: context)
55
+
56
+ try addAnimation(
57
+ for: .strokeStart,
58
+ keyframes: strokeStartKeyframes.keyframes,
59
+ value: { strokeStart in
60
+ // Lottie animation files express stoke trims as a numerical percentage value
61
+ // (e.g. 25%, 50%, 100%) so we divide by 100 to get the decimal values
62
+ // expected by Core Animation (e.g. 0.25, 0.5, 1.0).
63
+ CGFloat(strokeStart.cgFloatValue) / CGFloat(pathMultiplier) / 100
64
+ }, context: context)
65
+
66
+ try addAnimation(
67
+ for: .strokeEnd,
68
+ keyframes: strokeEndKeyframes.keyframes,
69
+ value: { strokeEnd in
70
+ // Lottie animation files express stoke trims as a numerical percentage value
71
+ // (e.g. 25%, 50%, 100%) so we divide by 100 to get the decimal values
72
+ // expected by Core Animation (e.g. 0.25, 0.5, 1.0).
73
+ CGFloat(strokeEnd.cgFloatValue) / CGFloat(pathMultiplier) / 100
74
+ }, context: context)
75
+
76
+ return pathMultiplier
77
+ }
78
+ }
79
+
80
+ /// The number of times that a `CGPath` needs to be duplicated in order to support the animation's `Trim` keyframes
81
+ typealias PathMultiplier = Int
82
+
83
+ extension Trim {
84
+
85
+ // MARK: Fileprivate
86
+
87
+ /// The `strokeStart` and `strokeEnd` keyframes to apply to a `CAShapeLayer`,
88
+ /// plus a `pathMultiplier` that should be applied to the layer's `path` so that
89
+ /// trim values larger than 100% can be displayed properly.
90
+ fileprivate func caShapeLayerKeyframes(context: LayerAnimationContext) throws
91
+ -> (strokeStart: KeyframeGroup<Vector1D>, strokeEnd: KeyframeGroup<Vector1D>, pathMultiplier: PathMultiplier)
92
+ {
93
+ let strokeStart: KeyframeGroup<Vector1D>
94
+ let strokeEnd: KeyframeGroup<Vector1D>
95
+
96
+ // CAShapeLayer requires strokeStart to be less than strokeEnd. This
97
+ // isn't required by the Lottie schema, so some animations may have
98
+ // strokeStart and strokeEnd flipped. If we detect this is the case,
99
+ // then swap them.
100
+ if startValueIsAlwaysGreaterThanEndValue() {
101
+ strokeStart = end
102
+ strokeEnd = start
103
+ } else {
104
+ strokeStart = start
105
+ strokeEnd = end
106
+ }
107
+
108
+ // If there are no offsets, then the stroke values can be used as-is
109
+ guard
110
+ !offset.keyframes.isEmpty,
111
+ offset.keyframes.contains(where: { $0.value.cgFloatValue != 0 })
112
+ else {
113
+ return (strokeStart, strokeEnd, 1)
114
+ }
115
+
116
+ // Otherwise, adjust the stroke values to account for the offsets
117
+ // 1. Interpolate the keyframes so they are all on a linear timing function
118
+ // 2. Merge by summing the stroke values with the offset values
119
+ let interpolatedStrokeStart = strokeStart.manuallyInterpolateKeyframes()
120
+ let interpolatedStrokeEnd = strokeEnd.manuallyInterpolateKeyframes()
121
+ let interpolatedStrokeOffset = offset.manuallyInterpolateKeyframes()
122
+
123
+ let adjustedStrokeStart = try adjustKeyframesForTrimOffsets(
124
+ strokeKeyframes: interpolatedStrokeStart,
125
+ offsetKeyframes: interpolatedStrokeOffset,
126
+ context: context)
127
+
128
+ let adjustedStrokeEnd = try adjustKeyframesForTrimOffsets(
129
+ strokeKeyframes: interpolatedStrokeEnd,
130
+ offsetKeyframes: interpolatedStrokeOffset,
131
+ context: context)
132
+
133
+ // If maximum stroke value is larger than 100%, then we have to create copies of the path
134
+ // so the total path length includes the maximum stroke
135
+ let maximumStroke = adjustedStrokeEnd.map { $0.value.cgFloatValue }.max() ?? 100
136
+ let pathMultiplier = Int(ceil(maximumStroke / 100.0))
137
+
138
+ return (
139
+ strokeStart: KeyframeGroup<Vector1D>(keyframes: adjustedStrokeStart),
140
+ strokeEnd: KeyframeGroup<Vector1D>(keyframes: adjustedStrokeEnd),
141
+ pathMultiplier: pathMultiplier)
142
+ }
143
+
144
+ // MARK: Private
145
+
146
+ /// Checks whether or not the value for `trim.start` is greater
147
+ /// than the value for every `trim.end` at every keyframe.
148
+ private func startValueIsAlwaysGreaterThanEndValue() -> Bool {
149
+ let keyframeTimes = Set(start.keyframes.map { $0.time } + end.keyframes.map { $0.time })
150
+
151
+ let startInterpolator = KeyframeInterpolator(keyframes: start.keyframes)
152
+ let endInterpolator = KeyframeInterpolator(keyframes: end.keyframes)
153
+
154
+ for keyframeTime in keyframeTimes {
155
+ guard
156
+ let startAtTime = startInterpolator.value(frame: keyframeTime) as? Vector1D,
157
+ let endAtTime = endInterpolator.value(frame: keyframeTime) as? Vector1D
158
+ else { continue }
159
+
160
+ if startAtTime.cgFloatValue < endAtTime.cgFloatValue {
161
+ return false
162
+ }
163
+ }
164
+
165
+ return true
166
+ }
167
+
168
+ /// Adjusted stroke keyframes to account for offset keyframes by merging them into a single keyframe collection
169
+ ///
170
+ /// Since stroke keyframes and offset keyframes can be defined on different animation curves, they must be
171
+ /// manually interpolated prior to invoking this method. Manually interpolating the keyframes will redefine both
172
+ /// keyframe groups such that they can be interpolated linearly.
173
+ ///
174
+ /// - Precondition: The keyframes must be interpolated using `KeyframeGroup.manuallyInterpolateKeyframes()`
175
+ private func adjustKeyframesForTrimOffsets(
176
+ strokeKeyframes: ContiguousArray<Keyframe<Vector1D>>,
177
+ offsetKeyframes: ContiguousArray<Keyframe<Vector1D>>,
178
+ context _: LayerAnimationContext) throws -> ContiguousArray<Keyframe<Vector1D>>
179
+ {
180
+ guard
181
+ !strokeKeyframes.isEmpty,
182
+ !offsetKeyframes.isEmpty
183
+ else {
184
+ return strokeKeyframes
185
+ }
186
+
187
+ // Map each time to its corresponding stroke/offset keyframe
188
+ var timeMap = [AnimationFrameTime: [Keyframe<Vector1D>?]]()
189
+ for stroke in strokeKeyframes {
190
+ timeMap[stroke.time] = [stroke, nil]
191
+ }
192
+ for offset in offsetKeyframes {
193
+ if var existing = timeMap[offset.time] {
194
+ existing[1] = offset
195
+ timeMap[offset.time] = existing
196
+ } else {
197
+ timeMap[offset.time] = [nil, offset]
198
+ }
199
+ }
200
+
201
+ // Each time will be mapped to a new, adjusted keyframe
202
+ var output = ContiguousArray<Keyframe<Vector1D>>()
203
+ var lastKeyframe: Keyframe<Vector1D>?
204
+ var lastOffset: Keyframe<Vector1D>?
205
+
206
+ for (time, values) in timeMap.sorted(by: { $0.0 < $1.0 }) {
207
+ // Extract keyframe/offset associated with this timestamp
208
+ let keyframe = values[0]
209
+ let offset = values[1]
210
+ lastKeyframe = keyframe ?? lastKeyframe
211
+ lastOffset = offset ?? lastOffset
212
+
213
+ guard let currentKeyframe = lastKeyframe else {
214
+ // No keyframes are output until the first keyframe occurs
215
+ continue
216
+ }
217
+
218
+ guard let currentOffset = lastOffset else {
219
+ // Scalar isHold keyframes are not output as they offset the offset keyframes
220
+ if !(strokeKeyframes.count == 1 && currentKeyframe.isHold) {
221
+ output.append(currentKeyframe)
222
+ }
223
+ continue
224
+ }
225
+
226
+ // Compute the adjusted value by converting the offset value to a stroke value
227
+ let strokeValue = currentKeyframe.value.value
228
+ let offsetValue = currentOffset.value.value
229
+ let adjustedValue = strokeValue + (offsetValue / 360 * 100)
230
+
231
+ // The tangent values are all `nil` as the keyframes should have been manually interpolated
232
+ let adjustedKeyframe = Keyframe<Vector1D>(
233
+ value: Vector1D(adjustedValue),
234
+ time: time,
235
+ isHold: currentKeyframe.isHold,
236
+ inTangent: nil,
237
+ outTangent: nil,
238
+ spatialInTangent: nil,
239
+ spatialOutTangent: nil)
240
+
241
+ output.append(adjustedKeyframe)
242
+ }
243
+
244
+ return output
245
+ }
246
+ }
@@ -0,0 +1,95 @@
1
+ // Created by Cal Stephens on 1/10/22.
2
+ // Copyright © 2022 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ extension CAShapeLayer {
7
+
8
+ // MARK: Internal
9
+
10
+ /// Adds animations for the given `Rectangle` to this `CALayer`
11
+ @nonobjc
12
+ func addAnimations(
13
+ for star: Star,
14
+ context: LayerAnimationContext,
15
+ pathMultiplier: PathMultiplier)
16
+ throws
17
+ {
18
+ switch star.starType {
19
+ case .star:
20
+ try addStarAnimation(for: star, context: context, pathMultiplier: pathMultiplier)
21
+ case .polygon:
22
+ try addPolygonAnimation(for: star, context: context, pathMultiplier: pathMultiplier)
23
+ case .none:
24
+ break
25
+ }
26
+ }
27
+
28
+ // MARK: Private
29
+
30
+ @nonobjc
31
+ private func addStarAnimation(
32
+ for star: Star,
33
+ context: LayerAnimationContext,
34
+ pathMultiplier: PathMultiplier)
35
+ throws
36
+ {
37
+ try addAnimation(
38
+ for: .path,
39
+ keyframes: star.position.keyframes,
40
+ value: { position in
41
+ // We can only use one set of keyframes to animate a given CALayer keypath,
42
+ // so we currently animate `position` and ignore any other keyframes.
43
+ // TODO: Is there a way to support this properly?
44
+ BezierPath.star(
45
+ position: position.pointValue,
46
+ outerRadius: try star.outerRadius
47
+ .exactlyOneKeyframe(context: context, description: "outerRadius").value.cgFloatValue,
48
+ innerRadius: try star.innerRadius?
49
+ .exactlyOneKeyframe(context: context, description: "innerRadius").value.cgFloatValue ?? 0,
50
+ outerRoundedness: try star.outerRoundness
51
+ .exactlyOneKeyframe(context: context, description: "outerRoundness").value.cgFloatValue,
52
+ innerRoundedness: try star.innerRoundness?
53
+ .exactlyOneKeyframe(context: context, description: "innerRoundness").value.cgFloatValue ?? 0,
54
+ numberOfPoints: try star.points
55
+ .exactlyOneKeyframe(context: context, description: "points").value.cgFloatValue,
56
+ rotation: try star.rotation
57
+ .exactlyOneKeyframe(context: context, description: "rotation").value.cgFloatValue,
58
+ direction: star.direction)
59
+ .cgPath()
60
+ .duplicated(times: pathMultiplier)
61
+ },
62
+ context: context)
63
+ }
64
+
65
+ @nonobjc
66
+ private func addPolygonAnimation(
67
+ for star: Star,
68
+ context: LayerAnimationContext,
69
+ pathMultiplier: PathMultiplier)
70
+ throws
71
+ {
72
+ try addAnimation(
73
+ for: .path,
74
+ keyframes: star.position.keyframes,
75
+ value: { position in
76
+ // We can only use one set of keyframes to animate a given CALayer keypath,
77
+ // so we currently animate `position` and ignore any other keyframes.
78
+ // TODO: Is there a way to support this properly?
79
+ BezierPath.polygon(
80
+ position: position.pointValue,
81
+ numberOfPoints: try star.points
82
+ .exactlyOneKeyframe(context: context, description: "numberOfPoints").value.cgFloatValue,
83
+ outerRadius: try star.outerRadius
84
+ .exactlyOneKeyframe(context: context, description: "outerRadius").value.cgFloatValue,
85
+ outerRoundedness: try star.outerRoundness
86
+ .exactlyOneKeyframe(context: context, description: "outerRoundedness").value.cgFloatValue,
87
+ rotation: try star.rotation
88
+ .exactlyOneKeyframe(context: context, description: "rotation").value.cgFloatValue,
89
+ direction: star.direction)
90
+ .cgPath()
91
+ .duplicated(times: pathMultiplier)
92
+ },
93
+ context: context)
94
+ }
95
+ }
@@ -0,0 +1,70 @@
1
+ // Created by Cal Stephens on 2/10/22.
2
+ // Copyright © 2022 Airbnb Inc. All rights reserved.
3
+
4
+ import Foundation
5
+ import QuartzCore
6
+
7
+ // MARK: - StrokeShapeItem
8
+
9
+ /// A `ShapeItem` that represents a stroke
10
+ protocol StrokeShapeItem: OpacityAnimationModel {
11
+ var strokeColor: KeyframeGroup<Color>? { get }
12
+ var width: KeyframeGroup<Vector1D> { get }
13
+ var lineCap: LineCap { get }
14
+ var lineJoin: LineJoin { get }
15
+ var miterLimit: Double { get }
16
+ var dashPattern: [DashElement]? { get }
17
+ }
18
+
19
+ // MARK: - Stroke + StrokeShapeItem
20
+
21
+ extension Stroke: StrokeShapeItem {
22
+ var strokeColor: KeyframeGroup<Color>? { color }
23
+ }
24
+
25
+ // MARK: - GradientStroke + StrokeShapeItem
26
+
27
+ extension GradientStroke: StrokeShapeItem {
28
+ var strokeColor: KeyframeGroup<Color>? { nil }
29
+ }
30
+
31
+ // MARK: - CAShapeLayer + StrokeShapeItem
32
+
33
+ extension CAShapeLayer {
34
+ /// Adds animations for properties related to the given `Stroke` object (`strokeColor`, `lineWidth`, etc)
35
+ @nonobjc
36
+ func addStrokeAnimations(for stroke: StrokeShapeItem, context: LayerAnimationContext) throws {
37
+ lineJoin = stroke.lineJoin.caLineJoin
38
+ lineCap = stroke.lineCap.caLineCap
39
+ miterLimit = CGFloat(stroke.miterLimit)
40
+
41
+ if let strokeColor = stroke.strokeColor {
42
+ try addAnimation(
43
+ for: .strokeColor,
44
+ keyframes: strokeColor.keyframes,
45
+ value: \.cgColorValue,
46
+ context: context)
47
+ }
48
+
49
+ try addAnimation(
50
+ for: .lineWidth,
51
+ keyframes: stroke.width.keyframes,
52
+ value: \.cgFloatValue,
53
+ context: context)
54
+
55
+ try addOpacityAnimation(for: stroke, context: context)
56
+
57
+ if let (dashPattern, dashPhase) = stroke.dashPattern?.shapeLayerConfiguration {
58
+ lineDashPattern = try dashPattern.map {
59
+ try KeyframeGroup(keyframes: $0)
60
+ .exactlyOneKeyframe(context: context, description: "stroke dashPattern").value.cgFloatValue as NSNumber
61
+ }
62
+
63
+ try addAnimation(
64
+ for: .lineDashPhase,
65
+ keyframes: dashPhase,
66
+ value: \.cgFloatValue,
67
+ context: context)
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,205 @@
1
+ // Created by Cal Stephens on 12/17/21.
2
+ // Copyright © 2021 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ // MARK: - TransformModel
7
+
8
+ /// This protocol mirrors the interface of `Transform`,
9
+ /// but it also implemented by `ShapeTransform` to allow
10
+ /// both transform types to share the same animation implementation.
11
+ protocol TransformModel {
12
+ /// The anchor point of the transform.
13
+ var anchorPoint: KeyframeGroup<Vector3D> { get }
14
+
15
+ /// The position of the transform. This is nil if the position data was split.
16
+ var _position: KeyframeGroup<Vector3D>? { get }
17
+
18
+ /// The positionX of the transform. This is nil if the position property is set.
19
+ var _positionX: KeyframeGroup<Vector1D>? { get }
20
+
21
+ /// The positionY of the transform. This is nil if the position property is set.
22
+ var _positionY: KeyframeGroup<Vector1D>? { get }
23
+
24
+ /// The scale of the transform
25
+ var scale: KeyframeGroup<Vector3D> { get }
26
+
27
+ /// The rotation of the transform. Note: This is single dimensional rotation.
28
+ var rotation: KeyframeGroup<Vector1D> { get }
29
+ }
30
+
31
+ // MARK: - Transform + TransformModel
32
+
33
+ extension Transform: TransformModel {
34
+ var _position: KeyframeGroup<Vector3D>? { position }
35
+ var _positionX: KeyframeGroup<Vector1D>? { positionX }
36
+ var _positionY: KeyframeGroup<Vector1D>? { positionY }
37
+ }
38
+
39
+ // MARK: - ShapeTransform + TransformModel
40
+
41
+ extension ShapeTransform: TransformModel {
42
+ var anchorPoint: KeyframeGroup<Vector3D> { anchor }
43
+ var _position: KeyframeGroup<Vector3D>? { position }
44
+ var _positionX: KeyframeGroup<Vector1D>? { nil }
45
+ var _positionY: KeyframeGroup<Vector1D>? { nil }
46
+ }
47
+
48
+ // MARK: - CALayer + TransformModel
49
+
50
+ extension CALayer {
51
+
52
+ // MARK: Internal
53
+
54
+ /// Adds transform-related animations from the given `TransformModel` to this layer
55
+ /// - This _doesn't_ apply `transform.opacity`, which has to be handled separately
56
+ /// since child layers don't inherit the `opacity` of their parent.
57
+ @nonobjc
58
+ func addTransformAnimations(for transformModel: TransformModel, context: LayerAnimationContext) throws {
59
+ try addPositionAnimations(from: transformModel, context: context)
60
+ try addAnchorPointAnimation(from: transformModel, context: context)
61
+ try addScaleAnimations(from: transformModel, context: context)
62
+ try addRotationAnimation(from: transformModel, context: context)
63
+ }
64
+
65
+ // MARK: Private
66
+
67
+ @nonobjc
68
+ private func addPositionAnimations(
69
+ from transformModel: TransformModel,
70
+ context: LayerAnimationContext)
71
+ throws
72
+ {
73
+ if let positionKeyframes = transformModel._position?.keyframes {
74
+ try addAnimation(
75
+ for: .position,
76
+ keyframes: positionKeyframes,
77
+ value: \.pointValue,
78
+ context: context)
79
+ } else if
80
+ let xKeyframes = transformModel._positionX?.keyframes,
81
+ let yKeyframes = transformModel._positionY?.keyframes
82
+ {
83
+ try addAnimation(
84
+ for: .positionX,
85
+ keyframes: xKeyframes,
86
+ value: \.cgFloatValue,
87
+ context: context)
88
+
89
+ try addAnimation(
90
+ for: .positionY,
91
+ keyframes: yKeyframes,
92
+ value: \.cgFloatValue,
93
+ context: context)
94
+ } else {
95
+ try context.logCompatibilityIssue("""
96
+ `Transform` values must provide either `position` or `positionX` / `positionY` keyframes
97
+ """)
98
+ }
99
+ }
100
+
101
+ @nonobjc
102
+ private func addAnchorPointAnimation(
103
+ from transformModel: TransformModel,
104
+ context: LayerAnimationContext)
105
+ throws
106
+ {
107
+ try addAnimation(
108
+ for: .anchorPoint,
109
+ keyframes: transformModel.anchorPoint.keyframes,
110
+ value: { absoluteAnchorPoint in
111
+ guard bounds.width > 0, bounds.height > 0 else {
112
+ context.logger.assertionFailure("Size must be non-zero before an animation can be played")
113
+ return .zero
114
+ }
115
+
116
+ // Lottie animation files express anchorPoint as an absolute point value,
117
+ // so we have to divide by the width/height of this layer to get the
118
+ // relative decimal values expected by Core Animation.
119
+ return CGPoint(
120
+ x: CGFloat(absoluteAnchorPoint.x) / bounds.width,
121
+ y: CGFloat(absoluteAnchorPoint.y) / bounds.height)
122
+ },
123
+ context: context)
124
+ }
125
+
126
+ @nonobjc
127
+ private func addScaleAnimations(
128
+ from transformModel: TransformModel,
129
+ context: LayerAnimationContext)
130
+ throws
131
+ {
132
+ try addAnimation(
133
+ for: .scaleX,
134
+ keyframes: transformModel.scale.keyframes,
135
+ value: { scale in
136
+ // Lottie animation files express scale as a numerical percentage value
137
+ // (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values
138
+ // expected by Core Animation (e.g. 0.5, 1.0, 2.0).
139
+ // - Negative `scale.x` values aren't applied correctly by Core Animation.
140
+ // This appears to be because we animate `transform.scale.x` and `transform.scale.y`
141
+ // as separate `CAKeyframeAnimation`s instead of using a single animation of `transform` itself.
142
+ // https://openradar.appspot.com/FB9862872
143
+ // - To work around this, we set up a `rotationY` animation below
144
+ // to flip the view horizontally, which gives us the desired effect.
145
+ abs(CGFloat(scale.x) / 100)
146
+ },
147
+ context: context)
148
+
149
+ // When `scale.x` is negative, we have to rotate the view
150
+ // half way around the y axis to flip it horizontally.
151
+ // - We don't do this in snapshot tests because it breaks the tests
152
+ // in surprising ways that don't happen at runtime. Definitely not ideal.
153
+ if TestHelpers.snapshotTestsAreRunning {
154
+ if transformModel.scale.keyframes.contains(where: { $0.value.x < 0 }) {
155
+ context.logger.warn("""
156
+ Negative `scale.x` values are not displayed correctly in snapshot tests
157
+ """)
158
+ }
159
+ } else {
160
+ try addAnimation(
161
+ for: .rotationY,
162
+ keyframes: transformModel.scale.keyframes,
163
+ value: { scale in
164
+ if scale.x < 0 {
165
+ return .pi
166
+ } else {
167
+ return 0
168
+ }
169
+ },
170
+ context: context)
171
+ }
172
+
173
+ try addAnimation(
174
+ for: .scaleY,
175
+ keyframes: transformModel.scale.keyframes,
176
+ value: { scale in
177
+ // Lottie animation files express scale as a numerical percentage value
178
+ // (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values
179
+ // expected by Core Animation (e.g. 0.5, 1.0, 2.0).
180
+ // - Negative `scaleY` values are correctly applied (they flip the view
181
+ // vertically), so we don't have to apply an additional rotation animation
182
+ // like we do for `scaleX`.
183
+ CGFloat(scale.y) / 100
184
+ },
185
+ context: context)
186
+ }
187
+
188
+ private func addRotationAnimation(
189
+ from transformModel: TransformModel,
190
+ context: LayerAnimationContext)
191
+ throws
192
+ {
193
+ try addAnimation(
194
+ for: .rotation,
195
+ keyframes: transformModel.rotation.keyframes,
196
+ value: { rotationDegrees in
197
+ // Lottie animation files express rotation in degrees
198
+ // (e.g. 90º, 180º, 360º) so we covert to radians to get the
199
+ // values expected by Core Animation (e.g. π/2, π, 2π)
200
+ rotationDegrees.cgFloatValue * .pi / 180
201
+ },
202
+ context: context)
203
+ }
204
+
205
+ }
@@ -0,0 +1,37 @@
1
+ // Created by Cal Stephens on 12/21/21.
2
+ // Copyright © 2021 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ extension CALayer {
7
+ /// Adds an animation for the given `inTime` and `outTime` to this `CALayer`
8
+ @nonobjc
9
+ func addVisibilityAnimation(
10
+ inFrame: AnimationFrameTime,
11
+ outFrame: AnimationFrameTime,
12
+ context: LayerAnimationContext)
13
+ {
14
+ let animation = CAKeyframeAnimation(keyPath: #keyPath(isHidden))
15
+ animation.calculationMode = .discrete
16
+
17
+ animation.values = [
18
+ true, // hidden, before `inFrame`
19
+ false, // visible
20
+ true, // hidden, after `outFrame`
21
+ ]
22
+
23
+ // From the documentation of `keyTimes`:
24
+ // - If the calculationMode is set to discrete, the first value in the array
25
+ // must be 0.0 and the last value must be 1.0. The array should have one more
26
+ // entry than appears in the values array. For example, if there are two values,
27
+ // there should be three key times.
28
+ animation.keyTimes = [
29
+ NSNumber(value: 0.0),
30
+ NSNumber(value: max(Double(context.progressTime(for: inFrame)), 0)),
31
+ NSNumber(value: min(Double(context.progressTime(for: outFrame)), 1)),
32
+ NSNumber(value: 1.0),
33
+ ]
34
+
35
+ add(animation, timedWith: context)
36
+ }
37
+ }